本文介紹的內(nèi)容是組件通信的常用方式:@Input、@Output、@ViewChild、模板變量、MessageService、Broadcaster (Angular 1.x $rootScope 中 $on、$broadcast ) 和 Pub - Sub 模式、RxJS Subject 存在的問(wèn)題。
counter.component.ts
import { Component, Input } from '@angular/core';
@Component({
selector: 'exe-counter',
template: `
<p>當(dāng)前值: {{ 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.ts
import { Component } from '@angular/core';
@Component({
selector: 'exe-app',
template: `
<exe-counter [count]="initialCount"></exe-counter>
`
})
export class AppComponent {
initialCount: number = 5;
}
counter.component.ts
import { Component, Input, Output, EventEmitter } from '@angular/core';
@Component({
selector: 'exe-counter',
template: `
<p>當(dāng)前值: {{ 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.ts
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事件已觸發(fā),當(dāng)前值是: ${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: `
<child-component #child></child-component>
<button (click)="child.name = childName">設(shè)置子組件名稱</button>
`
})
export class ParentComponent implements OnInit {
private childName: string;
constructor() { }
ngOnInit() {
this.childName = 'child-component';
}
}
child.component.ts
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'exe-child',
template: `
<p>Child Component</p>
`
})
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: `
<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.ts
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();
}
}
實(shí)現(xiàn) Angular 1.x 中的 $rootScope 對(duì)象中 $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');
}
}
本文主要是介紹組件通訊的思路,提供的都是相對(duì)簡(jiǎn)單的示例。如果想深入了解,請(qǐng)參考 - angular.cn - component-communication。
1.在實(shí)際開發(fā)中,我們也經(jīng)常使用 Pub (發(fā)布) - Sub (訂閱模式) 來(lái)實(shí)現(xiàn)模塊之間的消息通訊。接下來(lái)我們看一下 Pub - Sub 的核心點(diǎn):
key-value
結(jié)構(gòu)進(jìn)行消息類型和觀察者列表的存儲(chǔ)。具體示例如下(詳細(xì)信息,請(qǐng)參考 - [Pub/Sub JavaScript Object](Pub/Sub JavaScript Object)):
var events = (function(){
var topics = {};
var hOP = topics.hasOwnProperty;
return {
subscribe: function(topic, listener) {
// 如果topic類型不存在,則創(chuàng)建
if(!hOP.call(topics, topic)) topics[topic] = [];
// 添加listener
var index = topics[topic].push(listener) -1;
// 返回對(duì)象用于移除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 在使用中存在一個(gè)問(wèn)題,就是如果某個(gè) observer (觀察者) 在執(zhí)行的時(shí)候出現(xiàn)異常,卻沒(méi)有進(jìn)行異常處理,那么就會(huì)影響到其它的觀察者。解決該問(wèn)題,最簡(jiǎn)單的方式就是為所有的觀察者添加異常處理。具體問(wèn)題如下:
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);
以上代碼運(yùn)行后,控制臺(tái)的輸出結(jié)果:
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);
關(guān)于 RxJS Subject 的詳細(xì)信息,請(qǐng)查看 - RxJS Subject。
更多建議: