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