观察者模式的一种运用

场景

如上图,在一个歌曲详情模块,假设有2个子模块,歌曲模块和评论模块。

在歌曲模块和评论模块中都有评论数量这个属性,当用户在评论模块发布了一条评论后,评论模块和歌曲模块的数量要同步更新。

评论模块的数量很好更新,歌曲模块的评论数量怎么同步更新呢?

下面介绍几种实现方法。
1. 双向绑定实现

思路:

在AppComponent将评论数量count作为输入参数(@Input)传递给CommentComponent和MusicComponent,当AppComponent中的评论数量改变时,MucisComponent中的评论数量也会同时改变。
当CommentComponent更新评论数量count时,通过输出参数(@Output)更新AppComponent中的评论数量count,MusicComponent中的评论数量count就会跟着改变了。

AppComponent

import { Component } from '@angular/core';

@Component({
  selector: 'app-root',template: `<div>
              <app-music [count]="count"></app-music>
              <app-comment [(count)]="count"></app-comment>
             </div>`
})
export class AppComponent {
  count:number = 0;

  constructor() {

  }
}

MusicComponent

import { Component,Input } from '@angular/core';

@Component({
  selector: 'app-music',template: `<div>
    <h3>歌曲模块</h3>
    <div>评论数:<strong>{{ count }}</strong></div>
    </div>`,styleUrls: ['./music.component.css']
})
export class MusicComponent{
  // 评论数
  @Input()
  count:number;

  constructor() { }

}

CommentComponent

import { Component,EventEmitter,Input,Output } from '@angular/core';

@Component({
  selector: 'app-comment',template: `<div>
              <h3>评论</h3>
              <div>评论数:<strong>{{ count }}</strong></div>
              <div>
                <textarea #content></textarea>
              </div>
              <div>
                <button (click)="send(content)">发布</button>
              </div>
              <ul>
                <li *ngFor="let item of comments">
                  {{ item }}
                </li>
              </ul>
            </div>`
})
export class CommentComponent {
  // 评论数
  @Input()
  count:number = 0;

  // 评论数改变事件
  @Output()
  countChange:EventEmitter<number> = new EventEmitter<number>();

  // 评论列表
  comments:string [] = [];

  constructor() { }

  // 发送评论
  send(content) {
    if (content.value) {
      this.comments.push(content.value);
      this.count++;
      this.countChange.emit(this.count);
    }
  }

}

2. 观察者模式实现

思路

构建一个观察者对象,观察者对象拥有注册(regist),发布(fire),移除(remove)三个方法。在angular2中可以通过依赖注入注入到CommentComponent和MusicComponent。

在MusicComponent中注册(regist),‘count’ 事件。

当用户发送评论时,在CommentComponent中发布(fire),‘count’事件。

构造观察者对象

import { Injectable } from '@angular/core';

@Injectable()
export class ObserverService {
    /**
     * 消息队列
     * 用数组保存每一种消息的事件队列,
     * eventList['count'] = []
     * 注册消息时将事件回调函数push到事件队列
     * eventList['count'].push(fn)
     * 发布消息时依次执行消息队列中的每一个回调函数
     * for(let fn of eventList['count']) {
     *    fn(data);
     * }
     */ 
    eventList = {};

    constructor() {

    }

    /**
     * 发布消息
     * @param name 消息名
     * @param data 消息数据
     */
    fire(name:string,data:any) {
        if (this.eventList[name]) {
            const fns = this.eventList[name];
            for(let fn of fns) {
                fn(data);
            }
        }
    }

    /**
     * 注册消息
     * @param name 
     * @param fn 
     */
    regist(name:string,fn:any) {
        if (typeof fn === 'function') {
            const fns = this.eventList[name];
            if (fns) {
                fns.push(fn);
            } else {
                this.eventList[name] = [fn];
            }
        }
    }

    /**
     * 移除消息
     * @param name 
     * @param fn 
     */
    remove(name:string,fn:any) {
        const fns = this.eventList[name];
        if (fns) {
            if ( fn ) {
                let i;
                for(i = 0; i < fns.length; i++) {
                    if (fns[i] === fn) {
                        break;
                    }
                }
                if (i < fns.length) {
                    fns.splice(i,1);
                }
            } else {
                fns.length = 0;
            }
        }
    }
}

AppComponent

import { Component } from '@angular/core';

@Component({
  selector: 'app-root',template: `<div>
              <app-music></app-music>
              <app-comment></app-comment>
             </div>`
})
export class AppComponent {
  constructor() {

  }
}

MusicComponent

import { ObserverService } from '../service/observer.service';
import { Component,styleUrls: ['./music.component.css']
})
export class MusicComponent{
  // 评论数
  count:number;

  constructor(private observiceService:ObserverService) { 
    // 注册消息
    this.observiceService.regist('count',count => this.count = count);
  }

}

CommentComponent

import { ObserverService } from '../service/observer.service.2';
import { Component,template: `<div>
              <h3>评论</h3>
              <div>评论数:<strong>{{ count }}</strong></div>
              <div>
                <textarea #content></textarea>
              </div>
              <div>
                <button (click)="send(content)">发布</button>
              </div>
              <ul>
                <li *ngFor="let item of comments">
                  {{ item }}
                </li>
              </ul>
            </div>`
})
export class CommentComponent {
  // 评论数
  count:number = 0;

  // 评论列表
  comments:string [] = [];

  constructor(private observiceService:ObserverService) { }

  send(content) {
    if (content.value) {
      this.comments.push(content.value);
      this.count++;
      // 发布消息
      this.observiceService.fire('count',this.count);
    }
  }

}

3. 使用Rxjs中的Subject实现

subject参考

SubjectService

import { Observable,Subject } from 'rxjs/Rx';
import { Injectable } from '@angular/core';

@Injectable()
export class SubjectService {

count:number = 0;
comment:Subject<number>;

constructor() {
    this.comment = new Subject<number>();
}

}

AppComponent

import { Component } from '@angular/core';

@Component({
  selector: 'app-root',template: `<div>
              <app-music></app-music>
              <app-comment></app-comment>
             </div>`
})
export class AppComponent {
  constructor() {

  }
}

MusicComponent

import { SubjectService } from '../service/subject.service';
import { Component,styleUrls: ['./music.component.css']
})
export class MusicComponent{
  // 评论数
  count:number;

  constructor(private subjectService:SubjectService) { 
    // 注册消息
    this.subjectService.comment.subscribe(count => this.count = count);
  }

}

4. 小结

  • 在使用双向绑定进行多个的模块间的数据传输时,可以看到,当需要传递某个变量时,我们需要在每个模块中构造对应的@Input和@Output,借助父模块来进行传递数据,过程比较繁琐。
    AppComponent

CommentComponent

MusicComponent

  • 采用观察者模式,通过其订阅发布机制,可以简化数据传输的过程,使每个模块独立起来,我们的模块能更加专注于业务代码的编写。
    AppComponent

MusicComponent

CommentComponent

  • 借助Rxjs的Subject,我们能很方便的实现同样的功能。

相关文章

ANGULAR.JS:NG-SELECTANDNG-OPTIONSPS:其实看英文文档比看中...
AngularJS中使用Chart.js制折线图与饼图实例  Chart.js 是...
IE浏览器兼容性后续前言 继续尝试解决IE浏览器兼容性问题,...
Angular实现下拉菜单多选写这篇文章时,引用文章地址如下:h...
在AngularJS应用中集成科大讯飞语音输入功能前言 根据项目...
Angular数据更新不及时问题探讨前言 在修复控制角标正确变...