本文介绍的内容是组件通信的常用方式:@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: ` <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.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>当前值: {{ 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事件已触发,当前值是: ${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">设置子组件名称</button> ` }) 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: ` <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'; } }
使用 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<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(); } }
使用 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<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',error => console.log('B Error:' + error) ); subject.subscribe( x => console.log('C',error => console.log('C Error:' + error) ); source.subscribe(subject);
关于 RxJS Subject 的详细信息,请查看 - RxJS Subject。
原文链接:https://www.f2er.com/angularjs/147588.html