本文介绍的内容是组件通信的常用方式:@Input、@Output、@ViewChild、模板变量、MessageService、Broadcaster(Angular1.x$roo
本文介绍的内容是组件通信的常用方式:@Input、@Output、@ViewChild、模板变量、MessageService、Broadcaster (Angular 1.x $rootScope 中 $on、$broadcast ) 和 Pub – Sub 模式、RxJS Subject 存在的问题。
输入属性 (父组件 -> 子组件) counter.component.ts
import { Component, Input } from '@angular/core'; @Component({ selector: 'exe-counter', template: ` 当前值: {{ count }}
+ - ` }) export class CounterComponent { @Input() count: number = 0; increment() { this.count++; } decrement() { this.count--; } }
app.component.ts
import { Component } from '@angular/core'; @Component({ selector: 'exe-app', template: ` ` }) export class AppComponent { initialCount: number = 5; }
输出属性 (子组件 -> 父组件) counter.component.ts
import { Component, Input, Output, EventEmitter } from '@angular/core'; @Component({ selector: 'exe-counter', template: ` 当前值: {{ count }}
+ - ` }) export class CounterComponent { @Input() count: number = 0; @Output() change: EventEmitter = new EventEmitter(); increment() { this.count++; this.change.emit(this.count); } decrement() { this.count--; this.change.emit(this.count); } }
app.component.ts
import { Component } from '@angular/core'; @Component({ selector: 'exe-app', template: ` {{changeMsg}}
(change)="countChange($event)"> ` }) export class AppComponent { initialCount: number = 5; changeMsg: string; countChange(event: number) { this.changeMsg = `子组件change事件已触发,当前值是: ${event}`; } }
模板变量 child.component.ts
import {Component} from '@angular/core'; @Component({ selector: 'child-component', template: `I'm {{ name }}` }) export class ChildComponent { public name: string; }
parent.component.ts
import {Component, OnInit} from '@angular/core'; import {ChildComponent} from './child-component.ts'; @Component({ selector: 'parent-component', template: ` 设置子组件名称 ` }) export class ParentComponent implements OnInit { private childName: string; constructor() { } ngOnInit() { this.childName = 'child-component'; } }
@ViewChild 装饰器 child.component.ts
import { Component, OnInit } from '@angular/core'; @Component({ selector: 'exe-child', template: ` Child Component
` }) export class ChildComponent { name: string = ''; }
app.component.ts
import { Component, ViewChild, AfterViewInit } from '@angular/core'; import { ChildComponent } from './child.component'; @Component({ selector: 'my-app', template: ` Welcome to Angular World `, }) export class AppComponent { @ViewChild(ChildComponent) childCmp: ChildComponent; ngAfterViewInit() { this.childCmp.name = 'child-component'; } }
使用 MessageService – 基于 RxJS Subject message.service.ts
import { Injectable } from '@angular/core'; import {Observable} from 'rxjs/Observable'; import { Subject } from 'rxjs/Subject'; @Injectable() export class MessageService { private subject = new Subject(); sendMessage(message: string) { this.subject.next({ text: message }); } clearMessage() { this.subject.next(); } getMessage(): Observable { return this.subject.asObservable(); } }
home.component.ts
import { Component } from '@angular/core'; import { MessageService } from './message.service'; @Component({ selector: 'exe-home', template: `
Home Send Message Clear Message
` }) export class HomeComponent { constructor(private messageService: MessageService) {} sendMessage(): void { this.messageService.sendMessage('Message from Home Component to App Component!'); } clearMessage(): void { this.messageService.clearMessage(); } }app.component.ts
import { Component, OnDestroy } from '@angular/core'; import { Subscription } from 'rxjs/Subscription'; import { MessageService } from './message.service'; @Component({ selector: 'my-app', template: ` ` }) export class AppComponent implements OnDestroy { message: any; subscription: Subscription; constructor(private messageService: MessageService) { this.subscription = this.messageService .getMessage().subscribe( message => { this.message = message; }); } ngOnDestroy() { this.subscription.unsubscribe(); } }
使用 Broadcaster – 基于 RxJS Subject 实现 Angular 1.x 中的 $rootScope 对象中 $on
和 $broadcast
的功能。
broadcaster.ts
import { Injectable } from '@angular/core'; import {Subject} from 'rxjs/Subject'; import {Observable} from 'rxjs/Observable'; import 'rxjs/add/operator/filter'; import 'rxjs/add/operator/map'; interface BroadcastEvent { key: any; data?: any; } @Injectable() export class Broadcaster { private _eventBus: Subject; constructor() { this._eventBus = new Subject(); } broadcast(key: any, data?: any) { this._eventBus.next({key, data}); } on(key: any): Observable { return this._eventBus.asObservable() .filter(event => event.key === key) .map(event => event.data); } }
child.component.ts
import { Component } from '@angular/core'; @Component({ selector: 'child' }) export class ChildComponent { constructor(private broadcaster: Broadcaster) {} registerStringBroadcast() { this.broadcaster.on('MyEvent') .subscribe(message => { ... }); } emitStringBroadcast() { this.broadcaster.broadcast('MyEvent', 'some message'); } }
本文主要是介绍组件通讯的思路,提供的都是相对简单的示例。如果想深入了解,请参考 – angular.cn – component-communication。
我有话说 1.在实际开发中,我们也经常使用 Pub (发布) – Sub (订阅模式) 来实现模块之间的消息通讯。接下来我们看一下 Pub – Sub 的核心点:
至少包含 subscribe() 和 publish() 两个方法,subscribe() 用于实现消息订阅,publish() 方法用于发布消息。
支持订阅不同的消息类型,且对于任何一种消息类型都可以添加多个观察者。内部实现一般使用 key-value
结构进行消息类型和观察者列表的存储。
订阅观察者后,最好返回一个对象或函数对象,用于取消订阅。
具体示例如下(详细信息,请参考 – Pub/Sub Javascript Object):
var events = (function(){ var topics = {}; var hOP = topics.hasOwnProperty; return { subscribe: function(topic, listener) { // 如果topic类型不存在,则创建 if(!hOP.call(topics, topic)) topics[topic] = []; // 添加listener var index = topics[topic].push(listener) -1; // 返回对象用于移除listener return { remove: function() { delete topics[topic][index]; } }; }, publish: function(topic, info) { if(!hOP.call(topics, topic)) return; topics[topic].forEach(function(item) { item(info != undefined ? info : {}); }); } }; })();
使用示例:
var subscription = events.subscribe('/page/load', function(obj) { // 事件处理 }); events.publish('/page/load', { url: '/some/url/path' });
2.RxJS Subject 在使用中存在一个问题,就是如果某个 observer (观察者) 在执行的时候出现异常,却没有进行异常处理,那么就会影响到其它的观察者。解决该问题,最简单的方式就是为所有的观察者添加异常处理。具体问题如下:
const source = Rx.Observable.interval(1000); const subject = new Rx.Subject(); const example = subject.map(x => { if (x === 1) { throw new Error('oops'); } return x; }); subject.subscribe(x => console.log('A', x)); example.subscribe(x => console.log('B', x)); subject.subscribe(x => console.log('C', x)); source.subscribe(subject);
以上代码运行后,控制台的输出结果:
A 0 B 0 C 0 A 1 Rx.min.js:74 Uncaught Error: oops
解决方案:
const source = Rx.Observable.interval(1000); const subject = new Rx.Subject(); const example = subject.map(x => { if (x === 1) { throw new Error('oops'); } return x; }); subject.subscribe( x => console.log('A', x), error => console.log('A Error:' + error) );example.subscribe( x => console.log('B', x), error => console.log('B Error:' + error) ); subject.subscribe( x => console.log('C', x), error => console.log('C Error:' + error) ); source.subscribe(subject);
关于 RxJS Subject 的详细信息,请查看 – RxJS Subject。