本文介紹的內容是組件通訊的經常使用方式:@Input、@Output、@ViewChild、模板變量、MessageService、Broadcaster (Angular 1.x $rootScope 中 $on、$broadcast ) 和 Pub - Sub 模式、RxJS Subject 存在的問題。javascript
counter.component.tshtml
import { Component, Input } from '@angular/core'; @Component({ selector: 'exe-counter', template: ` <p>當前值: {{ count }}</p> <button (click)="increment()"> + </button> <button (click)="decrement()"> - </button> ` }) export class CounterComponent { @Input() count: number = 0; increment() { this.count++; } decrement() { this.count--; } }
app.component.tsjava
import { Component } from '@angular/core'; @Component({ selector: 'exe-app', template: ` <exe-counter [count]="initialCount"></exe-counter> ` }) export class AppComponent { initialCount: number = 5; }
counter.component.tstypescript
import { Component, Input, Output, EventEmitter } from '@angular/core'; @Component({ selector: 'exe-counter', template: ` <p>當前值: {{ count }}</p> <button (click)="increment()"> + </button> <button (click)="decrement()"> - </button> ` }) export class CounterComponent { @Input() count: number = 0; @Output() change: EventEmitter<number> = new EventEmitter<number>(); increment() { this.count++; this.change.emit(this.count); } decrement() { this.count--; this.change.emit(this.count); } }
app.component.tssegmentfault
import { Component } from '@angular/core'; @Component({ selector: 'exe-app', template: ` <p>{{changeMsg}}</p> <exe-counter [count]="initialCount" (change)="countChange($event)"></exe-counter> ` }) export class AppComponent { initialCount: number = 5; changeMsg: string; countChange(event: number) { this.changeMsg = `子組件change事件已觸發,當前值是: ${event}`; } }
child.component.tsapp
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: ` <child-component #child></child-component> <button (click)="child.name = childName">設置子組件名稱</button> ` }) export class ParentComponent implements OnInit { private childName: string; constructor() { } ngOnInit() { this.childName = 'child-component'; } }
child.component.tsoop
import { Component, OnInit } from '@angular/core'; @Component({ selector: 'exe-child', template: ` <p>Child Component</p> ` }) export class ChildComponent { name: string = ''; }
app.component.tsthis
import { Component, ViewChild, AfterViewInit } from '@angular/core'; import { ChildComponent } from './child.component'; @Component({ selector: 'my-app', template: ` <h4>Welcome to Angular World</h4> <exe-child></exe-child> `, }) export class AppComponent { @ViewChild(ChildComponent) childCmp: ChildComponent; ngAfterViewInit() { this.childCmp.name = 'child-component'; } }
message.service.tsurl
import { Injectable } from '@angular/core'; import {Observable} from 'rxjs/Observable'; import { Subject } from 'rxjs/Subject'; @Injectable() export class MessageService { private subject = new Subject<any>(); sendMessage(message: string) { this.subject.next({ text: message }); } clearMessage() { this.subject.next(); } getMessage(): Observable<any> { return this.subject.asObservable(); } }
home.component.ts
import { Component } from '@angular/core'; import { MessageService } from './message.service'; @Component({ selector: 'exe-home', template: ` <div> <h1>Home</h1> <button (click)="sendMessage()">Send Message</button> <button (click)="clearMessage()">Clear Message</button> </div>` }) 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: ` <div> <div *ngIf="message">{{message.text}}</div> <exe-home></exe-home> </div> ` }) 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(); } }
實現 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<BroadcastEvent>; constructor() { this._eventBus = new Subject<BroadcastEvent>(); } broadcast(key: any, data?: any) { this._eventBus.next({key, data}); } on<T>(key: any): Observable<T> { return this._eventBus.asObservable() .filter(event => event.key === key) .map(event => <T>event.data); } }
child.component.ts
import { Component } from '@angular/core'; @Component({ selector: 'child' }) export class ChildComponent { constructor(private broadcaster: Broadcaster) {} registerStringBroadcast() { this.broadcaster.on<string>('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。