你能在服务中注入 signalR in Angular 10+ 而不调用 app.component 中的方法吗
Can you inject signalR in Angular 10+ in a service without calling the methods in the app.component
所以我遵循了一些教程和一个常见主题,一旦您在 C#
中设置了您的集线器,您就会调用与此类似的服务:
private hubConnection: signalR.HubConnection = new signalR.HubConnectionBuilder()
.withUrl(`${environment.hub}contacts`)
.build();
constructor(private http: HttpClient) {
}
public startConnection = () =>
this.hubConnection
.start()
.then(() => console.log('Hub Connection started'))
.catch(err => console.log('Error while starting connection: ' + err))
public addTransferContactDataListener = () =>
this.hubConnection.on('transfercontactdata', (data) => {
this.contacts = data.result // where the magic happens on the push
console.log(data, 'Hub data transfer occuring');
});
我担心的是,如果您尝试在构造函数中注入 private hubConnection: signalR.HubConnection
,它就会崩溃。即使您设置了连接生成器。这很重要,因为如果我想要全部 subscribe
的四页或更多页怎么办?
我一直在做的是在 app.component.ts
中设置服务,然后调用 startConnection
和 addTransferContactDataListener
的方法。但这似乎是错误的。我试图让它在模块中可注入,但它总是失败,说它没有 provider
或其他东西。虽然它是可注入的,但你仍然必须调用它,而且在我的实践中,构造函数有时似乎被忽略了。
有没有人深入研究过在注入时调用和设置它并重用它? 像 'A' 或 'B' 视图调用 signalR
服务并且任何一个都可以自动将它 运行 作为构造函数或某个参数一次。我可能做错了什么。就像我说的,它有效。但是我必须调用 app.component.ts
中的方法,这样做感觉不对而且很脆弱。
在 Angular
中使用 SignalR
我们的 objective 是创建一个服务,作为 Angular 和我们的 signalR 连接之间的中介。这种方法意味着我们需要解决两个一般性问题
- 正在连接 SignalR
- 设计一种方法来与我们的应用程序的其余部分进行交互
Angular 接口
我们的目标是能够在我们的应用程序中的任何地方注入我们的服务,并在我们的服务感兴趣的 signalR 中心发生事件时让我们的组件做出适当的反应
使服务可用
由于我们的服务可能会在任何地方使用,并且具有处理 connectionHub 的生命周期逻辑,如果尝试打开一个非关闭的连接将会失败,我们唯一可以使用的注入器如下:
app.module.ts
@Injectable({providedIn: 'root'})
最简单的解决方案是使用 @Injectable({providedIn: 'root'})
装饰器,但是对于具有细微内部生命周期的服务(例如 SignalR 服务),我更愿意公开 public API 这样我们只公开我们团队可以安全使用的方法。
public接口
首先,让我们创建一个接口,我们可以使用该接口使 SignalR 服务在我们的应用程序的其余部分可用。但是,因为我们无法在 Angular 中提供接口,所以我们使用抽象 class 代替。
public SignalR 接口
import {Observable} from 'rxjs';
import {Injectable} from '@angular/core';
@Injectable()
export abstract class SignalRService {
abstract getDataStream(): Observable<any>
}
我们的 SignalR 服务开始
import {Subject, Observable} from 'rxjs';
import {SignalRService} from './signalr.service';
import {Injectable} from '@angular/core';
@Injectable()
export class PrivateSignalRService extends SignalRService {
private _signalEvent: Subject<any>;
constructor() {
super();
this._signalEvent = new Subject<any>();
}
getDataStream(): Observable<any> {
return this._signalEvent.asObservable();
}
}
现在我们有了注射的基本设置。我们在摘要 class 中描述了我们希望为其提供服务的 public 接口,将其视为接口,并在 PrivateSignalRService
中实现我们的逻辑
现在,我们剩下要做的就是告诉 Angular 注入器在我们请求 SignalRService
时提供 PrivateSignalRService
app.module.ts
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
AppRoutingModule
],
providers: [{
provide: SignalRService,
useClass: PrivateSignalRService
}],
bootstrap: [AppComponent]
})
export class AppModule {
// inject signal directly in module if you want to start connecting immediately.
constructor(private signal: SignalRService) {
}
}
将 SignalR 事件映射到主题事件
我们现在可以在我们的应用程序中注入我们的 SignalRService
,但是我们的 HubConnection 可能会随着时间的推移而增长,并且可能并非每个事件都与每个组件相关,因此我们创建了一个过滤器。
首先我们创建一个枚举,它代表我们可能期望接收的每个不同的 HubConnection 事件。
信号事件-type.ts
export enum SignalEventType {
EVENT_ONE,
EVENT_TWO
}
接下来我们需要一个接口,以便我们知道在调用 getDataStream
时会发生什么
信号事件
import {SignalEventType} from './signal-event-type';
export interface SignalEvent<TDataShape> {
type: SignalEventType,
data: TDataShape
}
更改我们的public抽象class接口
的签名
SignalRService
@Injectable()
export abstract class SignalRService {
abstract getDataStream<TDataShape>(...filterValues: SignalEventType[]): Observable<SignalEvent<TDataShape>>
}
PrivateSignalRService
@Injectable()
export class PrivateSignalRService extends SignalRService {
private _signalEvent: BehaviorSubject<SignalEvent<any>>;
constructor() {
super();
this._signalEvent = new BehaviorSubject<any>(null);
}
getDataStream<TDataShape>(...filterValues: SignalEventType[]): Observable<SignalEvent<TDataShape>> {
return this._signalEvent.asObservable().pipe(filter(event => filterValues.some(f => f === event.type)));
}
}
正在连接 SignalR
注意:这个例子使用了@aspnet/signalr
包。
观察 PrivateSignalRService
的以下变化
@Injectable()
export class PrivateSignalRService extends SignalRService {
private _signalEvent: Subject<SignalEvent<any>>;
private _openConnection: boolean = false;
private _isInitializing: boolean = false;
private _hubConnection!: HubConnection;
constructor() {
super();
this._signalEvent = new Subject<any>();
this._isInitializing = true;
this._initializeSignalR();
}
getDataStream<TDataShape>(...filterValues: SignalEventType[]): Observable<SignalEvent<TDataShape>> {
this._ensureConnection();
return this._signalEvent.asObservable().pipe(filter(event => filterValues.some(f => f === event.type)));
}
private _ensureConnection() {
if (this._openConnection || this._isInitializing) return;
this._initializeSignalR();
}
private _initializeSignalR() {
this._hubConnection = new HubConnectionBuilder()
.withUrl('https://localhost:5001/signalHub')
.build();
this._hubConnection.start()
.then(_ => {
this._openConnection = true;
this._isInitializing = false;
this._setupSignalREvents()
})
.catch(error => {
console.warn(error);
this._hubConnection.stop().then(_ => {
this._openConnection = false;
})
});
}
private _setupSignalREvents() {
this._hubConnection.on('MessageHelloWorld', (data) => {
// map or transform your data as appropriate here:
this._onMessage({type: SignalEventType.EVENT_ONE, data})
})
this._hubConnection.on('MessageNumberArray', (data) => {
// map or transform your data as appropriate here:
const {numbers} = data;
this._onMessage({type: SignalEventType.EVENT_TWO, data: numbers})
})
this._hubConnection.onclose((e) => this._openConnection = false);
}
private _onMessage<TDataShape>(payload: SignalEvent<TDataShape>) {
this._signalEvent.next(payload);
}
}
现在,您第一次请求 getDataStream
如果没有打开的连接,该服务将尝试创建一个 signalR 连接,因此您不再需要在 AppModule
中注入该服务构造函数。
监听组件中的事件
- 范例一对
EVENT_ONE
感兴趣
- 示例二对
EVENT_TWO
感兴趣
示例-one.component.ts
@Component({
selector: 'app-example-one',
template: `<p>Example One Component</p>`
})
export class ExampleOneComponent implements OnInit, OnDestroy {
subscription!: Subscription;
constructor(private signal: SignalRService) {
}
ngOnInit(): void {
this.subscription = this.signal.getDataStream<string>(SignalEventType.EVENT_ONE).subscribe(message => {
console.log(message.data);
})
}
ngOnDestroy() {
this.subscription.unsubscribe();
}
}
示例-two.component.ts
@Component({
selector: 'app-example-two',
template: `<p>Example Two Component</p>`
})
export class ExampleTwoComponent implements OnInit, OnDestroy {
subscription!: Subscription;
constructor(private signal: SignalRService) {
}
ngOnInit(): void {
this.subscription = this.signal.getDataStream<string[]>(SignalEventType.EVENT_TWO).subscribe(message => {
message.data.forEach(m => console.log(m));
})
}
ngOnDestroy() {
this.subscription.unsubscribe();
}
}
ExampleOneComponent
和 ExampleTwoComponent
现在仅当 HubConnection 中收到的事件是每个组件的适当类型时才接收事件。
最后的笔记
此示例代码没有可靠的错误处理,仅演示了我们将 signalR 与 Angular 集成的一般方法。
您还需要确定管理本地持久性的策略,因为主题将仅显示任何新传入消息,例如,当您在应用程序中导航时,这将重置您的组件。
来源
所以我遵循了一些教程和一个常见主题,一旦您在 C#
中设置了您的集线器,您就会调用与此类似的服务:
private hubConnection: signalR.HubConnection = new signalR.HubConnectionBuilder()
.withUrl(`${environment.hub}contacts`)
.build();
constructor(private http: HttpClient) {
}
public startConnection = () =>
this.hubConnection
.start()
.then(() => console.log('Hub Connection started'))
.catch(err => console.log('Error while starting connection: ' + err))
public addTransferContactDataListener = () =>
this.hubConnection.on('transfercontactdata', (data) => {
this.contacts = data.result // where the magic happens on the push
console.log(data, 'Hub data transfer occuring');
});
我担心的是,如果您尝试在构造函数中注入 private hubConnection: signalR.HubConnection
,它就会崩溃。即使您设置了连接生成器。这很重要,因为如果我想要全部 subscribe
的四页或更多页怎么办?
我一直在做的是在 app.component.ts
中设置服务,然后调用 startConnection
和 addTransferContactDataListener
的方法。但这似乎是错误的。我试图让它在模块中可注入,但它总是失败,说它没有 provider
或其他东西。虽然它是可注入的,但你仍然必须调用它,而且在我的实践中,构造函数有时似乎被忽略了。
有没有人深入研究过在注入时调用和设置它并重用它? 像 'A' 或 'B' 视图调用 signalR
服务并且任何一个都可以自动将它 运行 作为构造函数或某个参数一次。我可能做错了什么。就像我说的,它有效。但是我必须调用 app.component.ts
中的方法,这样做感觉不对而且很脆弱。
在 Angular
中使用 SignalR我们的 objective 是创建一个服务,作为 Angular 和我们的 signalR 连接之间的中介。这种方法意味着我们需要解决两个一般性问题
- 正在连接 SignalR
- 设计一种方法来与我们的应用程序的其余部分进行交互
Angular 接口
我们的目标是能够在我们的应用程序中的任何地方注入我们的服务,并在我们的服务感兴趣的 signalR 中心发生事件时让我们的组件做出适当的反应
使服务可用
由于我们的服务可能会在任何地方使用,并且具有处理 connectionHub 的生命周期逻辑,如果尝试打开一个非关闭的连接将会失败,我们唯一可以使用的注入器如下:
app.module.ts
@Injectable({providedIn: 'root'})
最简单的解决方案是使用 @Injectable({providedIn: 'root'})
装饰器,但是对于具有细微内部生命周期的服务(例如 SignalR 服务),我更愿意公开 public API 这样我们只公开我们团队可以安全使用的方法。
public接口
首先,让我们创建一个接口,我们可以使用该接口使 SignalR 服务在我们的应用程序的其余部分可用。但是,因为我们无法在 Angular 中提供接口,所以我们使用抽象 class 代替。
public SignalR 接口
import {Observable} from 'rxjs';
import {Injectable} from '@angular/core';
@Injectable()
export abstract class SignalRService {
abstract getDataStream(): Observable<any>
}
我们的 SignalR 服务开始
import {Subject, Observable} from 'rxjs';
import {SignalRService} from './signalr.service';
import {Injectable} from '@angular/core';
@Injectable()
export class PrivateSignalRService extends SignalRService {
private _signalEvent: Subject<any>;
constructor() {
super();
this._signalEvent = new Subject<any>();
}
getDataStream(): Observable<any> {
return this._signalEvent.asObservable();
}
}
现在我们有了注射的基本设置。我们在摘要 class 中描述了我们希望为其提供服务的 public 接口,将其视为接口,并在 PrivateSignalRService
现在,我们剩下要做的就是告诉 Angular 注入器在我们请求 SignalRService
PrivateSignalRService
app.module.ts
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
AppRoutingModule
],
providers: [{
provide: SignalRService,
useClass: PrivateSignalRService
}],
bootstrap: [AppComponent]
})
export class AppModule {
// inject signal directly in module if you want to start connecting immediately.
constructor(private signal: SignalRService) {
}
}
将 SignalR 事件映射到主题事件
我们现在可以在我们的应用程序中注入我们的 SignalRService
,但是我们的 HubConnection 可能会随着时间的推移而增长,并且可能并非每个事件都与每个组件相关,因此我们创建了一个过滤器。
首先我们创建一个枚举,它代表我们可能期望接收的每个不同的 HubConnection 事件。
信号事件-type.ts
export enum SignalEventType {
EVENT_ONE,
EVENT_TWO
}
接下来我们需要一个接口,以便我们知道在调用 getDataStream
信号事件
import {SignalEventType} from './signal-event-type';
export interface SignalEvent<TDataShape> {
type: SignalEventType,
data: TDataShape
}
更改我们的public抽象class接口
的签名SignalRService
@Injectable()
export abstract class SignalRService {
abstract getDataStream<TDataShape>(...filterValues: SignalEventType[]): Observable<SignalEvent<TDataShape>>
}
PrivateSignalRService
@Injectable()
export class PrivateSignalRService extends SignalRService {
private _signalEvent: BehaviorSubject<SignalEvent<any>>;
constructor() {
super();
this._signalEvent = new BehaviorSubject<any>(null);
}
getDataStream<TDataShape>(...filterValues: SignalEventType[]): Observable<SignalEvent<TDataShape>> {
return this._signalEvent.asObservable().pipe(filter(event => filterValues.some(f => f === event.type)));
}
}
正在连接 SignalR
注意:这个例子使用了@aspnet/signalr
包。
观察 PrivateSignalRService
@Injectable()
export class PrivateSignalRService extends SignalRService {
private _signalEvent: Subject<SignalEvent<any>>;
private _openConnection: boolean = false;
private _isInitializing: boolean = false;
private _hubConnection!: HubConnection;
constructor() {
super();
this._signalEvent = new Subject<any>();
this._isInitializing = true;
this._initializeSignalR();
}
getDataStream<TDataShape>(...filterValues: SignalEventType[]): Observable<SignalEvent<TDataShape>> {
this._ensureConnection();
return this._signalEvent.asObservable().pipe(filter(event => filterValues.some(f => f === event.type)));
}
private _ensureConnection() {
if (this._openConnection || this._isInitializing) return;
this._initializeSignalR();
}
private _initializeSignalR() {
this._hubConnection = new HubConnectionBuilder()
.withUrl('https://localhost:5001/signalHub')
.build();
this._hubConnection.start()
.then(_ => {
this._openConnection = true;
this._isInitializing = false;
this._setupSignalREvents()
})
.catch(error => {
console.warn(error);
this._hubConnection.stop().then(_ => {
this._openConnection = false;
})
});
}
private _setupSignalREvents() {
this._hubConnection.on('MessageHelloWorld', (data) => {
// map or transform your data as appropriate here:
this._onMessage({type: SignalEventType.EVENT_ONE, data})
})
this._hubConnection.on('MessageNumberArray', (data) => {
// map or transform your data as appropriate here:
const {numbers} = data;
this._onMessage({type: SignalEventType.EVENT_TWO, data: numbers})
})
this._hubConnection.onclose((e) => this._openConnection = false);
}
private _onMessage<TDataShape>(payload: SignalEvent<TDataShape>) {
this._signalEvent.next(payload);
}
}
现在,您第一次请求 getDataStream
如果没有打开的连接,该服务将尝试创建一个 signalR 连接,因此您不再需要在 AppModule
中注入该服务构造函数。
监听组件中的事件
- 范例一对
EVENT_ONE
感兴趣
- 示例二对
EVENT_TWO
感兴趣
示例-one.component.ts
@Component({
selector: 'app-example-one',
template: `<p>Example One Component</p>`
})
export class ExampleOneComponent implements OnInit, OnDestroy {
subscription!: Subscription;
constructor(private signal: SignalRService) {
}
ngOnInit(): void {
this.subscription = this.signal.getDataStream<string>(SignalEventType.EVENT_ONE).subscribe(message => {
console.log(message.data);
})
}
ngOnDestroy() {
this.subscription.unsubscribe();
}
}
示例-two.component.ts
@Component({
selector: 'app-example-two',
template: `<p>Example Two Component</p>`
})
export class ExampleTwoComponent implements OnInit, OnDestroy {
subscription!: Subscription;
constructor(private signal: SignalRService) {
}
ngOnInit(): void {
this.subscription = this.signal.getDataStream<string[]>(SignalEventType.EVENT_TWO).subscribe(message => {
message.data.forEach(m => console.log(m));
})
}
ngOnDestroy() {
this.subscription.unsubscribe();
}
}
ExampleOneComponent
和 ExampleTwoComponent
现在仅当 HubConnection 中收到的事件是每个组件的适当类型时才接收事件。
最后的笔记
此示例代码没有可靠的错误处理,仅演示了我们将 signalR 与 Angular 集成的一般方法。
您还需要确定管理本地持久性的策略,因为主题将仅显示任何新传入消息,例如,当您在应用程序中导航时,这将重置您的组件。