本文的主要内容是介绍父子组件通讯时,如何传递异步的数据。咱们将经过一个具体的示例,来介绍不一样的处理方式。假设咱们有一个博客组件,用来显示博主信息和分类的帖子列表信息。具体以下图所示:typescript
了解完具体需求后,接下来咱们来一步步实现该组件。json
post.interface.tssegmentfault
// each post will have a title and category export interface Post { title: string; category: string; } // grouped posts by category export interface GroupPosts { category: string; posts: Post[]; }
[ { "title": "Functional Programming", "category": "RxJS" }, { "title": "Angular 2 Component Inheritance", "category": "NG2" }, { "title": "RxJS Operators", "category": "RxJS" }, { "title": "Angular 2 Http Module - HTTP", "category": "WEB" }, { "title": "RxJS Observable", "category": "RxJS" }, { "title": "Angular 2 AsyncPipe", "category": "NG2" } ]
posts.component.ts数组
import { Component, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core'; import { BehaviorSubject } from 'rxjs/BehaviorSubject'; import { Post, GroupPosts } from './post.interface'; @Component({ selector: 'exe-posts', template: ` <div class="list-group"> <div *ngFor="let group of groupPosts;" class="list-group-item"> <h4>{{ group.category }}</h4> <ul> <li *ngFor="let post of group.posts"> {{ post.title }} </li> </ul> <div> </div> ` }) export class PostsComponent implements OnInit, OnChanges { @Input() data: Post[]; groupPosts: GroupPosts[]; ngOnInit() {} ngOnChanges(changes: SimpleChanges) { console.dir(changes); } groupByCategory(data: Post[]): GroupPosts[] { if (!data) return; // 获取目录名,使用ES6中Set进行去重处理 const categories = new Set(data.map(x => x.category)); // 从新生成二维数组,用于页面显示 const result = Array.from(categories).map(x => ({ category: x, posts: data.filter(post => post.category === x) })); return result; } }
blogger.component.ts浏览器
import { Component, OnInit, Input } from '@angular/core'; import { Observable } from 'rxjs/Observable'; import { Observer } from 'rxjs/Observer'; import { Post } from './post.interface'; @Component({ selector: 'exe-bloggers', template: ` <h1>Posts by: {{ blogger }}</h1> <div> <exe-posts [data]="posts"></exe-posts> </div> ` }) export class BloggerComponent implements OnInit { blogger = 'Semlinker'; posts: Post[]; ngOnInit() { this.getPostsByBlogger() .subscribe(posts => this.posts = posts); } getPostsByBlogger(): Observable<Post[]> { return Observable.create((observer: Observer<Post[]>) => { setTimeout(() => { const posts = [ { "title": "Functional Programming", "category": "RxJS" }, { "title": "Angular 2 Component Inheritance", "category": "NG2" }, { "title": "RxJS Operators", "category": "RxJS" }, { "title": "Angular 2 Http Module - HTTP", "category": "WEB" }, { "title": "RxJS Observable", "category": "RxJS" }, { "title": "Angular 2 AsyncPipe", "category": "NG2" } ]; observer.next(posts); }, 2000); }) } }
app.component.tsapp
import { Component } from '@angular/core'; @Component({ selector: 'exe-app', template: ` <exe-bloggers></exe-bloggers> ` }) export class AppComponent {}
以上代码运行完后,你会发现浏览器中只显示 Posts by: Semlinker
。那咱们要怎么正确显示帖子列表呢?异步
使用 *ngIf
指令延迟 exe-posts 组件的初始化,须要调整的代码以下:async
blogger.component.tspost
template: ` <h1>Posts by: {{ blogger }}</h1> <div *ngIf="posts"> <exe-posts [data]="posts"></exe-posts> </div> `
posts.component.ts性能
ngOnInit() { this.groupPosts = this.groupByCategory(this.data); }
当数据绑定输入属性的值发生变化的时候,Angular 将会主动调用 ngOnChanges() 方法。它会得到一个 SimpleChanges 对象,包含绑定属性的新值和旧值,所以咱们能够利用 ngOnChanges()
钩子,执行 groupPosts 的数据初始化操做。须要调整的代码以下:
1.移除 blogger.component.ts 组件模板中的 *ngIf
指令:
template: ` <h1>Posts by: {{ blogger }}</h1> <div> <exe-posts [data]="posts"></exe-posts> </div> `
2.更新 posts.component.ts
ngOnInit() { // this.groupPosts = this.groupByCategory(this.data); } ngOnChanges(changes: SimpleChanges) { if (changes['data']) { this.groupPosts = this.groupByCategory(this.data); } }
咱们也能够利用 RxJS 中 BehaviorSubject
来监测变化。须要注意的是,在使用 Observable 或 Subject 时,在不使用的时候,须要及时取消订阅,以免出现内存泄露的问题。具体代码以下:
posts.component.ts
import { Component, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core'; import { BehaviorSubject } from 'rxjs/BehaviorSubject'; import 'rxjs/add/operator/takeWhile'; import { Post, GroupPosts } from './post.interface'; @Component({ selector: 'exe-posts', template: ` <div class="list-group"> <div *ngFor="let group of groupPosts;" class="list-group-item"> <h4>{{ group.category }}</h4> <ul> <li *ngFor="let post of group.posts"> {{ post.title }} </li> </ul> <div> </div> ` }) export class PostsComponent implements OnInit, OnChanges { private _data$ = new BehaviorSubject<Post[]>([]); @Input() set data(value: Post[]) { this._data$.next(value); } get data(): Post[] { return this._data$.getValue(); } groupPosts: GroupPosts[]; ngOnInit() { this._data$ // 当this.groupPosts有值的时候,会自动取消订阅 .takeWhile(() => !this.groupPosts) .subscribe(x => { this.groupPosts = this.groupByCategory(this.data); }); } ngOnChanges(changes: SimpleChanges) { } groupByCategory(data: Post[]): GroupPosts[] { if (!data) return; // 获取目录名,使用ES6中Set进行去重处理 const categories = new Set(data.map(x => x.category)); // 从新生成二维数组,用于页面显示 const result = Array.from(categories).map(x => ({ category: x, posts: data.filter(post => post.category === x) })); return result; } }
上面示例中,咱们使用了 RxJS 中 BehaviorSubject,若是想进一步了解详细信息,请参考 - RxJS Subject。
须要注意的是,若是使用 takeWhile
操做符,在页面成功显示后,若是输入属性又发生变化,页面是不会随着更新的,由于咱们已经取消订阅了。若是数据源会持续变化,那么能够移除 takeWhile
操做符,而后 ngOnDestroy()
钩子中执行取消订阅操做。
除了上面提到的三种方案外,咱们还可使用 Observable 对象。即设置输入属性的类型是 Observable。具体示例以下:
posts.component.ts
import { Component, Input, OnChanges, OnInit, OnDestroy, SimpleChanges } from '@angular/core'; import { Observable } from 'rxjs/Observable'; import { Subscription } from 'rxjs/Subscription'; import { Post, GroupPosts } from './post.interface'; @Component({ selector: 'exe-posts', template: ` <div class="list-group"> <div *ngFor="let group of groupPosts;" class="list-group-item"> <h4>{{ group.category }}</h4> <ul> <li *ngFor="let post of group.posts"> {{ post.title }} </li> </ul> <div> </div> ` }) export class PostsComponent implements OnInit, OnChanges, OnDestroy { @Input() data: Observable<Post[]>; // 输入属性的类型是Observable groupPosts: GroupPosts[]; dataSubscription: Subscription; // 用于组件销毁时取消订阅 ngOnInit() { this.dataSubscription = this.data.subscribe(posts => { this.groupPosts = this.groupByCategory(posts); }); } ngOnChanges(changes: SimpleChanges) { } ngOnDestroy() { this.dataSubscription.unsubscribe(); } groupByCategory(data: Post[]): GroupPosts[] { if (!data) return; // 获取目录名,使用ES6中Set进行去重处理 const categories = new Set(data.map(x => x.category)); // 从新生成二维数组,用于页面显示 const result = Array.from(categories).map(x => ({ category: x, posts: data.filter(post => post.category === x) })); return result; } }
blogger.component.ts
import { Component, OnInit, Input } from '@angular/core'; import { Observable } from 'rxjs/Observable'; import { Observer } from 'rxjs/Observer'; import { Post } from './post.interface'; @Component({ selector: 'exe-bloggers', template: ` <h1>Posts by: {{ blogger }}</h1> <div> <exe-posts [data]="posts"></exe-posts> </div> ` }) export class BloggerComponent implements OnInit { blogger = 'Semlinker'; posts: Observable<Post[]>; ngOnInit() { this.posts = this.getPostsByBlogger(); } getPostsByBlogger(): Observable<Post[]> { return Observable.create((observer: Observer<Post[]>) => { setTimeout(() => { const posts = [ { "title": "Functional Programming", "category": "RxJS" }, { "title": "Angular 2 Component Inheritance", "category": "NG2" }, { "title": "RxJS Operators", "category": "RxJS" }, { "title": "Angular 2 Http Module - HTTP", "category": "WEB" }, { "title": "RxJS Observable", "category": "RxJS" }, { "title": "Angular 2 AsyncPipe", "category": "NG2" } ]; observer.next(posts); }, 2000); }) } }
上面的示例是没有在模板中直接使用 Observable 类型的输入属性,若须要在模板中直接使用的话,可使用 Angular 2 中的 AsyncPipe
,了解详细信息,请查看 - Angular 2 AsyncPipe。另外在 Angular 2 Change Detection - 2 文章中,咱们也有介绍怎么利用 Observable 提升 Angular 2 变化检测的性能,有兴趣的读者能够了解一下。
1.上面介绍的方案,应该使用哪种?
这个仍是取决于实际的应用场景,若是数据源只会发生一次变化,那么能够考虑使用 *ngIf
指令。若是数据源会持续变化,能够考虑使用其它的方案。
2.组件通讯还有哪些方式?
组件通讯的经常使用方式:@Input、@Output、@ViewChild、模板变量、MessageService、Broadcaster (Angular 1.x $rootScope 中 $on、$broadcast ) 等。
详细的信息,请参考 - Angular 2 Components Communicate