场景
如上图,在一个歌曲详情模块,假设有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, 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{
// 评论数
count:number;
constructor(private observiceService:ObserverService) {
// 注册消息
this.observiceService.regist('count', count => this.count = count);
}
}
CommentComponent
import { ObserverService } from '../service/observer.service.2';
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 {
// 评论数
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, 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{
// 评论数
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,我们能很方便的实现同样的功能。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。