IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    Angular 2 Components Communicate

    semlinker发表于 2017-04-06 10:16:48
    love 0

    本文介绍的内容是组件通信的常用方式:@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';
    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', 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。



沪ICP备19023445号-2号
友情链接