观察者模式的一种运用

前端之家收集整理的这篇文章主要介绍了观察者模式的一种运用前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

场景

如上图,在一个歌曲详情模块,假设有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,我们能很方便的实现同样的功能

猜你在找的Angularjs相关文章