SignalR在Angular中的应用
我们的目标是创建一个服务,作为Angular和signalR连接之间的中介。这种方法意味着我们需要解决两个普遍的问题
-
与SignalR连接
-
设计一种与我们的应用程序其他部分交互的方法
角度接口
我们的目标是能够在应用程序中的任何位置注入我们的服务,并在我们的signalR集线器中发生我们的服务感兴趣的事件时,让我们的组件做出适当的反应
提供服务
由于我们的服务可能会在任何地方使用,并且具有处理connectionHub的生命周期逻辑,如果试图打开非闭合连接,connectionHub将失败,因此我们只能使用以下注射器:
-
app.module.ts
-
@Injectable({providedIn: 'root'})
最简单的解决方案是利用
@可注入({providedIn:“root”})
decorator,但是对于具有细微内部生命周期的服务,例如SignalR服务,我更喜欢公开公共的API,这样我们只公开我们的团队可以安全使用的方法。
公共接口
首先,让我们创建一个接口,用于使SignalR服务在应用程序的其余部分中可用。然而,因为我们不能在Angular中提供接口,所以我们使用了一个抽象类。
公共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();
}
}
现在我们有了注射的基本设置。我们在抽象类中描述我们希望为其提供服务的公共接口,将其视为接口,并在中实现我们的逻辑
PrivateSignalRService
现在,我们所要做的就是告诉Angular Injector提供
PrivateSignalRService
每当我们要求
SignalRService
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事件映射到主题事件
我们现在可以注射
SignalRS服务
在我们的应用程序中,但我们的HubConnection可能会随着时间的推移而增长,而且可能不是每个事件都与每个组件相关,所以我们创建了一个过滤器。
首先,我们创建一个枚举,该枚举表示我们可能期望接收的每个不同HubConnection事件。
信号事件类型.ts
export enum SignalEventType {
EVENT_ONE,
EVENT_TWO
}
接下来我们需要一个接口,这样我们就知道调用时会发生什么
getDataStream
信号事件
import {SignalEventType} from './signal-event-type';
export interface SignalEvent<TDataShape> {
type: SignalEventType,
data: TDataShape
}
更改公共抽象类接口的签名
SignalRS服务
@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
组件示例.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();
}
}
示例-两个组件.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集成的一般方法。
您还需要确定管理本地持久性的策略,因为主题将只显示任何新的传入消息,例如,当您在应用程序中导航时,这些消息将重置您的组件。