Angular 5:单Service向多组件推送实时数据异常排查
问题分析与解决方案
你遇到的核心问题是每个组件都创建了独立的MqttProviderService实例,导致MQTT消息只会被最后初始化的那个实例接收,自然只有一个组件能拿到数据。
为什么会这样?
你在两个组件的@Component装饰器里都配置了providers: [{ provide: AbstractClassService, useClass: MqttProviderService }]——这意味着Angular会为每个组件单独创建一个MqttProviderService实例。而MQTT客户端的连接和消息回调绑定是在服务构造函数里初始化的,最后初始化的那个服务实例会覆盖(或者说独占)消息接收逻辑,所以只有对应的组件能收到数据。
解决方案:单例服务+RxJS Subject广播消息
我们需要让MqttProviderService成为应用级单例,同时用RxJS的Subject来实现消息的多组件订阅,具体步骤如下:
1. 改造抽象类,用Subject管理消息流
修改AbstractClassService,引入RxJS Subject来广播消息,这样多个组件可以订阅同一个消息源:
import { Injectable } from '@angular/core'; import { Subject } from 'rxjs/Subject'; import { Observable } from 'rxjs/Observable'; @Injectable() export abstract class AbstractClassService { // 内部使用Subject发送消息 private messageSubject = new Subject<string>(); // 对外暴露只读的Observable,供组件订阅 public message$: Observable<string> = this.messageSubject.asObservable(); protected emitMessage(msg: string): void { this.messageSubject.next(msg); } // 保留原抽象方法,让子类实现 public abstract onMessage(msg: string): void; }
2. 调整MQTT服务,触发消息广播
修改MqttProviderService的onMessage方法,调用父类的emitMessage来广播消息:
// ... 其他代码不变 public onMessage(msg: string) { // 将收到的MQTT消息发送给Subject this.emitMessage(msg); }
3. 把服务提升为应用级单例
移除两个组件里的providers配置,转而在**根模块(比如AppModule)**里注册服务,这样整个应用只会有一个MqttProviderService实例:
import { NgModule } from '@angular/core'; import { AbstractClassService } from './path/to/abstract-class.service'; import { MqttProviderService } from './path/to/mqtt-provider.service'; @NgModule({ // ... 其他模块配置(declarations、imports等) providers: [ { provide: AbstractClassService, useClass: MqttProviderService } // ... 其他应用级服务 ] }) export class AppModule { }
4. 修改组件,订阅消息流
组件1(GaugeComponent)
import { Component, OnInit, OnDestroy } from '@angular/core'; import { AbstractClassService } from '../../protocols/abstract-class.service'; import { Subscription } from 'rxjs/Subscription'; @Component({ selector: 'app-gauge', templateUrl: './gauge.component.html', // 移除providers配置 styleUrls: ['./gauge.component.css'] }) export class GaugeComponent implements OnInit, OnDestroy { data = [ { name: '', value: '' } ]; // 保存订阅对象,用于销毁时取消订阅 private messageSub: Subscription; constructor(private abstractclassService: AbstractClassService ) { } ngOnInit() { // 订阅消息流 this.messageSub = this.abstractclassService.message$.subscribe(msg => { this.data = [{name: 'test', value: msg}]; }); } ngOnDestroy() { // 销毁订阅,避免内存泄漏 this.messageSub.unsubscribe(); } }
组件2(注意类名不要和组件1重复!)
import { Component, OnInit, OnDestroy } from '@angular/core'; import { AbstractClassService } from '../../protocols/abstract-class.service'; import { Subscription } from 'rxjs/Subscription'; @Component({ selector: 'app-gauge1', templateUrl: './gauge1.component.html', // 移除providers配置 styleUrls: ['./gauge1.component.css'] }) export class Gauge1Component implements OnInit, OnDestroy { data = [ { name: '', series: [] }, ]; private messageSub: Subscription; constructor(private abstractclassService: AbstractClassService ) { } ngOnInit() { this.messageSub = this.abstractclassService.message$.subscribe(msg => { this.data[0].series.push({ 'value': msg, 'name': String(new Date().getSeconds())}); this.data = this.data.slice(); }); } ngOnDestroy() { this.messageSub.unsubscribe(); } }
关键说明
- 单例服务:通过在根模块注册服务,确保整个应用只有一个
MqttProviderService实例,MQTT连接和消息回调只会初始化一次。 - RxJS Subject:作为消息中转站,服务收到MQTT消息后广播给所有订阅的组件,实现多组件同时接收数据。
- 订阅销毁:在组件销毁时取消订阅,避免内存泄漏,这是Angular开发的最佳实践。
内容的提问来源于stack exchange,提问作者Steffn
相关产品推荐
相关产品推荐

