組件通信

2018-06-06 10:46 更新

本文介紹的內(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';
  }
}

@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

實(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。

我有話說(shuō)

1.在實(shí)際開發(fā)中,我們也經(jīng)常使用 Pub (發(fā)布) - Sub (訂閱模式) 來(lái)實(shí)現(xiàn)模塊之間的消息通訊。接下來(lái)我們看一下 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)):

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。

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

掃描二維碼

下載編程獅App

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

編程獅公眾號(hào)