組件通信

2018-06-06 10:46 更新

本文介紹的內(nèi)容是組件通信的常用方式:@Input、@Output、@ViewChild、模板變量、MessageService、Broadcaster (Angular 1.x $rootScope 中 $on、$broadcast ) 和 Pub - Sub 模式、RxJS Subject 存在的問題。

輸入屬性 (父組件 -> 子組件)

counter.component.ts

  1. import { Component, Input } from '@angular/core';
  2. @Component({
  3. selector: 'exe-counter',
  4. template: `
  5. <p>當(dāng)前值: {{ count }}</p>
  6. <button (click)="increment()"> + </button>
  7. <button (click)="decrement()"> - </button>
  8. `
  9. })
  10. export class CounterComponent {
  11. @Input() count: number = 0;
  12. increment() {
  13. this.count++;
  14. }
  15. decrement() {
  16. this.count--;
  17. }
  18. }

app.component.ts

  1. import { Component } from '@angular/core';
  2. @Component({
  3. selector: 'exe-app',
  4. template: `
  5. <exe-counter [count]="initialCount"></exe-counter>
  6. `
  7. })
  8. export class AppComponent {
  9. initialCount: number = 5;
  10. }

輸出屬性 (子組件 -> 父組件)

counter.component.ts

  1. import { Component, Input, Output, EventEmitter } from '@angular/core';
  2. @Component({
  3. selector: 'exe-counter',
  4. template: `
  5. <p>當(dāng)前值: {{ count }}</p>
  6. <button (click)="increment()"> + </button>
  7. <button (click)="decrement()"> - </button>
  8. `
  9. })
  10. export class CounterComponent {
  11. @Input() count: number = 0;
  12. @Output() change: EventEmitter<number> = new EventEmitter<number>();
  13. increment() {
  14. this.count++;
  15. this.change.emit(this.count);
  16. }
  17. decrement() {
  18. this.count--;
  19. this.change.emit(this.count);
  20. }
  21. }

app.component.ts

  1. import { Component } from '@angular/core';
  2. @Component({
  3. selector: 'exe-app',
  4. template: `
  5. <p>{{changeMsg}}</p>
  6. <exe-counter [count]="initialCount"
  7. (change)="countChange($event)"></exe-counter>
  8. `
  9. })
  10. export class AppComponent {
  11. initialCount: number = 5;
  12. changeMsg: string;
  13. countChange(event: number) {
  14. this.changeMsg = `子組件change事件已觸發(fā),當(dāng)前值是: ${event}`;
  15. }
  16. }

模板變量

child.component.ts

  1. import {Component} from '@angular/core';
  2. @Component({
  3. selector: 'child-component',
  4. template: `I'm {{ name }}`
  5. })
  6. export class ChildComponent {
  7. public name: string;
  8. }

parent.component.ts

  1. import {Component, OnInit} from '@angular/core';
  2. import {ChildComponent} from './child-component.ts';
  3. @Component({
  4. selector: 'parent-component',
  5. template: `
  6. <child-component #child></child-component>
  7. <button (click)="child.name = childName">設(shè)置子組件名稱</button>
  8. `
  9. })
  10. export class ParentComponent implements OnInit {
  11. private childName: string;
  12. constructor() { }
  13. ngOnInit() {
  14. this.childName = 'child-component';
  15. }
  16. }

@ViewChild 裝飾器

child.component.ts

  1. import { Component, OnInit } from '@angular/core';
  2. @Component({
  3. selector: 'exe-child',
  4. template: `
  5. <p>Child Component</p>
  6. `
  7. })
  8. export class ChildComponent {
  9. name: string = '';
  10. }

app.component.ts

  1. import { Component, ViewChild, AfterViewInit } from '@angular/core';
  2. import { ChildComponent } from './child.component';
  3. @Component({
  4. selector: 'my-app',
  5. template: `
  6. <h4>Welcome to Angular World</h4>
  7. <exe-child></exe-child>
  8. `,
  9. })
  10. export class AppComponent {
  11. @ViewChild(ChildComponent)
  12. childCmp: ChildComponent;
  13. ngAfterViewInit() {
  14. this.childCmp.name = 'child-component';
  15. }
  16. }

使用 MessageService - 基于 RxJS Subject

message.service.ts

  1. import { Injectable } from '@angular/core';
  2. import {Observable} from 'rxjs/Observable';
  3. import { Subject } from 'rxjs/Subject';
  4. @Injectable()
  5. export class MessageService {
  6. private subject = new Subject<any>();
  7. sendMessage(message: string) {
  8. this.subject.next({ text: message });
  9. }
  10. clearMessage() {
  11. this.subject.next();
  12. }
  13. getMessage(): Observable<any> {
  14. return this.subject.asObservable();
  15. }
  16. }

home.component.ts

  1. import { Component } from '@angular/core';
  2. import { MessageService } from './message.service';
  3. @Component({
  4. selector: 'exe-home',
  5. template: `
  6. <div>
  7. <h1>Home</h1>
  8. <button (click)="sendMessage()">Send Message</button>
  9. <button (click)="clearMessage()">Clear Message</button>
  10. </div>`
  11. })
  12. export class HomeComponent {
  13. constructor(private messageService: MessageService) {}
  14. sendMessage(): void {
  15. this.messageService.sendMessage('Message from Home Component to App Component!');
  16. }
  17. clearMessage(): void {
  18. this.messageService.clearMessage();
  19. }
  20. }

app.component.ts

  1. import { Component, OnDestroy } from '@angular/core';
  2. import { Subscription } from 'rxjs/Subscription';
  3. import { MessageService } from './message.service';
  4. @Component({
  5. selector: 'my-app',
  6. template: `
  7. <div>
  8. <div *ngIf="message">{{message.text}}</div>
  9. <exe-home></exe-home>
  10. </div>
  11. `
  12. })
  13. export class AppComponent implements OnDestroy {
  14. message: any;
  15. subscription: Subscription;
  16. constructor(private messageService: MessageService) {
  17. this.subscription = this.messageService
  18. .getMessage().subscribe( message => {
  19. this.message = message;
  20. });
  21. }
  22. ngOnDestroy() {
  23. this.subscription.unsubscribe();
  24. }
  25. }

使用 Broadcaster - 基于 RxJS Subject

實(shí)現(xiàn) Angular 1.x 中的 $rootScope 對(duì)象中 $on$broadcast 的功能。

broadcaster.ts

  1. import { Injectable } from '@angular/core';
  2. import {Subject} from 'rxjs/Subject';
  3. import {Observable} from 'rxjs/Observable';
  4. import 'rxjs/add/operator/filter';
  5. import 'rxjs/add/operator/map';
  6. interface BroadcastEvent {
  7. key: any;
  8. data?: any;
  9. }
  10. @Injectable()
  11. export class Broadcaster {
  12. private _eventBus: Subject<BroadcastEvent>;
  13. constructor() {
  14. this._eventBus = new Subject<BroadcastEvent>();
  15. }
  16. broadcast(key: any, data?: any) {
  17. this._eventBus.next({key, data});
  18. }
  19. on<T>(key: any): Observable<T> {
  20. return this._eventBus.asObservable()
  21. .filter(event => event.key === key)
  22. .map(event => <T>event.data);
  23. }
  24. }

child.component.ts

  1. import { Component } from '@angular/core';
  2. @Component({
  3. selector: 'child'
  4. })
  5. export class ChildComponent {
  6. constructor(private broadcaster: Broadcaster) {}
  7. registerStringBroadcast() {
  8. this.broadcaster.on<string>('MyEvent')
  9. .subscribe(message => {
  10. ...
  11. });
  12. }
  13. emitStringBroadcast() {
  14. this.broadcaster.broadcast('MyEvent', 'some message');
  15. }
  16. }

本文主要是介紹組件通訊的思路,提供的都是相對(duì)簡單的示例。如果想深入了解,請(qǐng)參考 - angular.cn - component-communication。

我有話說

1.在實(shí)際開發(fā)中,我們也經(jīng)常使用 Pub (發(fā)布) - Sub (訂閱模式) 來實(shí)現(xiàn)模塊之間的消息通訊。接下來我們看一下 Pub - Sub 的核心點(diǎn):

  • 至少包含 subscribe() 和 publish() 兩個(gè)方法,subscribe() 用于實(shí)現(xiàn)消息訂閱,publish() 方法用于發(fā)布消息。
  • 支持訂閱不同的消息類型,且對(duì)于任何一種消息類型都可以添加多個(gè)觀察者。內(nèi)部實(shí)現(xiàn)一般使用 key-value 結(jié)構(gòu)進(jìn)行消息類型和觀察者列表的存儲(chǔ)。
  • 訂閱觀察者后,最好返回一個(gè)對(duì)象或函數(shù)對(duì)象,用于取消訂閱。

具體示例如下(詳細(xì)信息,請(qǐng)參考 - [Pub/Sub JavaScript Object](Pub/Sub JavaScript Object)):

  1. var events = (function(){
  2. var topics = {};
  3. var hOP = topics.hasOwnProperty;
  4. return {
  5. subscribe: function(topic, listener) {
  6. // 如果topic類型不存在,則創(chuàng)建
  7. if(!hOP.call(topics, topic)) topics[topic] = [];
  8. // 添加listener
  9. var index = topics[topic].push(listener) -1;
  10. // 返回對(duì)象用于移除listener
  11. return {
  12. remove: function() {
  13. delete topics[topic][index];
  14. }
  15. };
  16. },
  17. publish: function(topic, info) {
  18. if(!hOP.call(topics, topic)) return;
  19. topics[topic].forEach(function(item) {
  20. item(info != undefined ? info : {});
  21. });
  22. }
  23. };
  24. })();

使用示例:

  1. var subscription = events.subscribe('/page/load', function(obj) {
  2. // 事件處理
  3. });
  4. events.publish('/page/load', {
  5. url: '/some/url/path'
  6. });

2.RxJS Subject 在使用中存在一個(gè)問題,就是如果某個(gè) observer (觀察者) 在執(zhí)行的時(shí)候出現(xiàn)異常,卻沒有進(jìn)行異常處理,那么就會(huì)影響到其它的觀察者。解決該問題,最簡單的方式就是為所有的觀察者添加異常處理。具體問題如下:

  1. const source = Rx.Observable.interval(1000);
  2. const subject = new Rx.Subject();
  3. const example = subject.map(x => {
  4. if (x === 1) {
  5. throw new Error('oops');
  6. }
  7. return x;
  8. });
  9. subject.subscribe(x => console.log('A', x));
  10. example.subscribe(x => console.log('B', x));
  11. subject.subscribe(x => console.log('C', x));
  12. source.subscribe(subject);

以上代碼運(yùn)行后,控制臺(tái)的輸出結(jié)果:

  1. A 0
  2. B 0
  3. C 0
  4. A 1
  5. Rx.min.js:74 Uncaught Error: oops

解決方案:

  1. const source = Rx.Observable.interval(1000);
  2. const subject = new Rx.Subject();
  3. const example = subject.map(x => {
  4. if (x === 1) {
  5. throw new Error('oops');
  6. }
  7. return x;
  8. });
  9. subject.subscribe(
  10. x => console.log('A', x),
  11. error => console.log('A Error:' + error)
  12. );
  13. example.subscribe(
  14. x => console.log('B', x),
  15. error => console.log('B Error:' + error)
  16. );
  17. subject.subscribe(
  18. x => console.log('C', x),
  19. error => console.log('C Error:' + error)
  20. );
  21. source.subscribe(subject);

關(guān)于 RxJS Subject 的詳細(xì)信息,請(qǐng)查看 - RxJS Subject。

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)