代码之家  ›  专栏  ›  技术社区  ›  djangojazz

你能在不调用app.component中的方法的情况下在服务中的Angular 10+中注入signalR吗

  •  0
  • djangojazz  · 技术社区  · 3 年前

    因此,一旦您在中设置了中心,我就遵循了一些教程和其中一个常见主题 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 这样做感觉不对而且很脆弱。

    0 回复  |  直到 3 年前
        1
  •  6
  •   Mikkel Christensen    1 年前

    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集成的一般方法。

    您还需要确定管理本地持久性的策略,因为主题将只显示任何新的传入消息,例如,当您在应用程序中导航时,这些消息将重置您的组件。