引言
在Angular开发中,组件间通信一直是个经典话题。今天要介绍的这种方法,是基于Angular Service特性与Registry模式,提炼出一个通用化的DataService。它带来的最大优势是——消息收发一步到位,无需再为每一条通信链路单独编写service代码。

背景
先快速回顾一下,在Angular中目前有哪些组件间通信的方式?
| 序号 | 通信方式 | 描述 |
|---|---|---|
| 1 | 输入属性(@Input) | 通过属性绑定,将数据从父组件传递给子组件。 |
| 2 | 输出属性(@Output) | 通过事件绑定,子组件可以发送事件给父组件,并传递数据。 |
| 3 | 父子组件直接访问 | 在某些情况下,父组件可以通过 ViewChild 或 ContentChild 装饰器直接访问子组件或模板中的元素。 |
| 4 | 服务(Service) | 创建共享的服务,组件可以注入该服务来存储和获取数据。 |
| 5 | RxJS Subject 和 Observable | 使用 RxJS 中的 Subject 和 Observable 来实现组件之间的消息传递。 |
| 6 | Angular 路由参数 | 通过路由参数在不同组件之间传递数据。 |
| 7 | NgRx | 使用 NgRx 状态管理库来实现更复杂的组件间通信和数据共享。 |
这些方法各有各的硬伤。要么代码量感人,要么学习曲线陡峭。尤其是当通信链条横跨多个组件时——比如从下图中的组件D绕到组件G,用前两种方式简直是噩梦。方式4和5通常是更常用的解法,但依然有点繁琐:每一组消息传递,都离不开一份专有的service代码。
组件A
/
组件B 组件C
/ /
组件D 组件E 组件F 组件G
本文介绍的方案,本质上是在方式4和5的基础上做了一层抽象封装,产出一个公共的DataService。使用它的结果就是:消息一收一发,中间的接缝代码全都不用自己写了。
常规方式实现组件间消息通信
为了对比,先来看看“方案4+5”通常是怎么实现的。这个组合通过Service与RxJS Subject的结合,能在任意组件间搭建通信桥梁。
首先是创建一个MessageService服务,负责在组件之间透传消息:
// message.service.ts
import { Injectable } from '@angular/core';
import { Subject } from 'rxjs';
@Injectable({
providedIn: 'root',
})
export class MessageService {
private messageSubject = new Subject();
// Observable string stream
message$ = this.messageSubject.asObservable();
// Service method to send a message
sendMessage(message: string) {
this.messageSubject.next(message);
}
}
然后就是经典的SenderComponent和ReceiverComponent:前者负责发送,后者负责接收。
// sender.component.ts
import { Component } from '@angular/core';
import { MessageService } from './message.service';
@Component({
selector: 'app-sender',
template: `
`,
})
export class SenderComponent {
message: string;
constructor(private messageService: MessageService) {}
sendMessage() {
this.messageService.sendMessage(this.message);
this.message = ''; // 清空输入框
}
}
// receiver.component.ts
import { Component } from '@angular/core';
import { MessageService } from './message.service';
@Component({
selector: 'app-receiver',
template: ` 接收到的消息: {{ receivedMessage }}
`,
})
export class ReceiverComponent {
receivedMessage: string;
constructor(private messageService: MessageService) {
this.messageService.message$.subscribe(message => {
this.receivedMessage = message;
});
}
}
在这个例子里,MessageService通过RxJS的Subject构造出一条可观察的消息流。SenderComponent调用sendMessage往里投递数据,ReceiverComponent用subscribe挂到这条流上去取消息。
这里有个关键细节:为了让MessageService成为全局可用的单例,@Injectable里指定了providedIn: 'root'。这样整个App的组件就能共享同一个服务实例。
当然,别忘了把SenderComponent和ReceiverComponent注册到模块里,同时要在模块模板里放上对应的组件选择器。
这样,SenderComponent发出的消息就会经过MessageService顺利传递到ReceiverComponent,并最终展示在模板中。
新的解决方案
要理解这个新方案,需先对Angular Service的共享机制和RxJS的多播模式有基本认识。
核心思路其实很简单:利用Service的单例特性——即同一模块内的组件共享同一个服务实例,配合Subject的天然多播能力,构建一个公共的Service,通过它来完成数据的跨组件传递。而之前的做法(比如message.service.ts)还需要为每一组通信单独写一个service文件、单独写一套流程。
理解这个方案有三个关键点:
单例:在@Injectable里设置providedIn: 'root',这个Service就成了全局共享的单一实例。因为是共享的,所以它能充当消息的传递载体。这也是整个方案的根基。
RxJS的多播:底层走的依旧是观察者模式,也就是发布-订阅。
注册表:为了复用Service、简化代码,这套方案引入了一个注册表,用来存放每个消息事件对应的Subject对象。Subject实例会在创建监听的地方——一般是需要接收消息的那一端——被创建出来。
下面是这个公共Service的核心代码:
// dataService.service.ts
import { Injectable } from '@angular/core';
import { Subject, Subscription } from 'rxjs';
@Injectable({
providedIn: 'root',
})
export class DataService {
// 创建注册表,用于存放监听器
private events = new Map();
/**
* 发送消息
*/
sendMessage(event: string, value: T): void {
if (!this.events.has(event)) {
return;
}
this.events.get(event).subject.next(value);
}
/**
* 获取监听器
*
* 注意:
* 1. getListener()应该在sendMessage()之前调用
* 2. 应放在ngOnInit()、ngAfterViewInit()等只会执行一次的生命周期函数中
*/
getListener(event: string): Subject {
// 多处监听时走此分支
if (this.events.has(event)) {
const current = this.events.get(event);
current.count++;
return current.subject;
}
const listener = {
count: 1,
subject: new Subject(),
};
// 监听器在创建监听时加入注册表
this.events.set(event, listener);
return listener.subject;
}
/**
* 取消订阅
*
* 必须手动取消订阅。
* 取消时检查监听者个数,没有监听者了就移除监听器。
*/
cancelSubscription(event: string, subscription: Subscription) {
if (!this.events.has(event)) {
return;
}
const current = this.events.get(event);
current.count--;
if (current.count === 0) {
this.events.delete(event);
}
subscription.unsubscribe();
}
}
使用的时候,只需要引入这个公共的DataService,然后直接调用它的API即可。
创建监听的示例:
// 接收传递过来的消息
receiveChangeMessage;
ngOnInit() {
...
this.subscription = this.dataService.getListener('event_name').subscribe(message => {
this.receiveChangeMessage = message;
});
...
}
ngOnDestroy() {
this.dataService.cancelSubscription('event_name', this.subscription);
}
发送消息的示例:
public onSomethingChange(value: boolean): void {
...
this.dataService.sendMessage('event_name', value);
}
给出一个较完整的使用示例
接下来看一个用上述DataService搭建通信的完整例子。
假设我们有两个组件:SenderComponent和ReceiverComponent,它们通过DataService来传递消息。
// sender.component.ts
import { Component } from '@angular/core';
import { DataService } from './data.service';
@Component({
selector: 'app-sender',
template: `
`,
})
export class SenderComponent {
message: string;
constructor(private dataService: DataService) {}
sendMessage() {
this.dataService.sendMessage('customEvent', this.message);
this.message = '';
}
}
// receiver.component.ts
import { Component, OnInit, OnDestroy } from '@angular/core';
import { DataService } from './data.service';
import { Subscription } from 'rxjs';
@Component({
selector: 'app-receiver',
template: ` 接收到的消息: {{ receivedMessage }}
`,
})
export class ReceiverComponent implements OnInit, OnDestroy {
receivedMessage: string;
subscription: Subscription;
constructor(private dataService: DataService) {}
ngOnInit() {
this.subscription = this.dataService.getListener('customEvent').subscribe(message => {
this.receivedMessage = message;
});
}
ngOnDestroy() {
this.dataService.cancelSubscription('customEvent', this.subscription);
}
}
在这个例子里,DataService的sendMessage方法负责投递消息,getListener方法负责订阅消息流,并在收到消息时更新ReceiverComponent的receivedMessage属性。cancelSubscription方法用于取消订阅,并在没有任何监听者时,从注册表里把对应的监听器清理掉。
同样别忘了把这两个组件注册进模块,并在模块模板中放入它们的选择器。
通过使用DataService,SenderComponent发出的消息成功传递到了ReceiverComponent并显示出来。这样,组件间的消息通信就顺滑地实现了。
实践拓展
这个思路后续还能继续进化,比如做成插件,以装饰器的形式来调用:
export class ReceiveComponent {
@listen('messageEvent')
message;
}
export class SendComponent {
@send('messageEvent')
message;
}
这种写法可以说是相当优雅了。以上就是本次分享的完整内容,也欢迎对前端技术感兴趣的朋友一起交流、参与共建。
关于 OpenTiny
OpenTiny 是一套企业级组件库解决方案,适配 PC / 移动端等多端场景,覆盖 Vue2 / Vue3 / Angular 多技术栈。它还包括主题配置系统、中后台模板、CLI 命令行等效率提升工具,能帮助开发者高效构建 Web 应用。
核心亮点:
跨端跨框架:基于 Renderless 无渲染组件设计架构,一套代码同时支持 Vue2 / Vue3、PC / Mobile,支持函数级别的逻辑定制和全模板替换,灵活性突出,二次开发能力强。组件丰富:PC 端 80+ 组件,移动端 30+ 组件,包含高频组件 Table、Tree、Select 等,内置虚拟滚动保证大数据场景下的流畅体验。除常见组件外,还有 Split 面板分割器、IpAddress IP 地址输入框、Calendar 日历、Crop 图片裁切等特色组件。配置式组件:组件支持模板式和配置式两种使用方式,非常适合低代码平台。团队已将 OpenTiny 集成到内部低代码平台,并针对低码场景做了大量优化。周边生态齐全:基于 Angular + TypeScript 的 TinyNG 组件库,提供 10+ 实用功能、20+ 典型页面的 TinyPro 中后台模板,覆盖前端开发全流程的 TinyCLI 工程化工具,以及强大的在线主题配置平台 TinyTheme。
