Angular 2 AsyncPipe

更新时间 - 2017-03-21 15:02

今天我们来介绍一下 Angular 2 中 AsyncPipe (异步管道) ,使用 AsyncPipe 我们可以直接在模板中使用 PromiseObservable 对象,而不用通过定义一个类的成员属性来存储返回的结果。

AsyncPipe 订阅一个 Observable 或 Promise 对象,并返回它发出的最新值。 当发出新值时,异步管道会主动调用变化检测器的 markForCheck() 方法,标识组件需执行变化检测。 当组件被销毁时,异步管道自动取消订阅,以避免潜在的内存泄漏。

AsyncPipe with Promise

Promise 未使用 AsyncPipe

promise.component.ts

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

@Component({
    selector: 'exe-promise',
    template: `
     

Promise Component

{{promiseData}}

` }) export class PromiseComponent { promiseData: string; constructor() { this.getPromise().then(v => this.promiseData = v); } getPromise(): Promise { return new Promise((resolve, reject) => { setTimeout(() => { resolve('Promise complete!'); }, 2000); }); } }

app.component.ts

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

@Component({
  selector: 'exe-app',
  template: `
   
  `
})
export class AppComponent { }

以上代码运行后浏览器显示的结果:

Angular 2 AsyncPipe_第1张图片

Promise 使用 AsyncPipe

promise-async-pipe.component.ts

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

@Component({
    selector: 'exe-promise-pipe',
    template: `
     

Promise with AsyncPipeComponent

{{ promise | async }}

` }) export class PromiseAsyncPipeComponent { promise: Promise; constructor() { this.promise = this.getPromise(); } getPromise(): Promise { return new Promise((resolve, reject) => { setTimeout(() => { resolve('Promise with AsyncPipe complete!'); }, 2000); }); } }

app.component.ts

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

@Component({
  selector: 'exe-app',
  template: `
   
  `
})
export class AppComponent { }

以上代码运行后浏览器显示的结果:

Angular 2 AsyncPipe_第2张图片

AsyncPipe with Observables

Observable 未使用 AsyncPipe

observable.component.ts

import { Observable, Subscription } from 'rxjs/Rx';
import { Component, OnDestroy } from '@angular/core';

@Component({
    selector: 'exe-observable',
    template: `
     

Observable Component

{{ observableData }}

` }) export class ObservableComponent implements OnDestroy { observableData: number; subscription: Subscription = null; constructor() { this.subscribeObservable(); } getObservable(): Observable { return Observable .interval(1000) // 每隔1秒,生成一个值 .take(10) // 获取前面10个数据 .map(v => v * v); // 对每个数据进行乘方处理 } subscribeObservable() { this.subscription = this.getObservable() .subscribe(v => this.observableData = v); } ngOnDestroy() { // 组件销毁时取消订阅,以避免潜在的内存泄漏 if (this.subscription) { this.subscription.unsubscribe(); } } }

app.component.ts

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

@Component({
  selector: 'exe-app',
  template: `
   
  `
})
export class AppComponent { }

以上代码运行后浏览器显示的结果:

Angular 2 AsyncPipe_第3张图片

Observable 使用 AsyncPipe

observable-async-pipe.component.ts

import { Component } from '@angular/core';
import { Observable } from 'rxjs/Rx';

@Component({
    selector: 'exe-observable-pipe',
    template: `
     

Observable with AsyncPipe Component

{{ observable | async }}

` }) export class ObservableAsyncPipeComponent { observable: Observable constructor() { this.observable = this.getObservable(); } getObservable(): Observable { return Observable .interval(1000) .take(10) .map(v => v * v); } }

app.component.ts

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

@Component({
  selector: 'exe-app',
  template: `
   
  `
})
export class AppComponent { }

以上代码运行后浏览器显示的结果:

Angular 2 AsyncPipe_第4张图片

为了更让读者更好地掌握 AsyncPipe, 我们再来一个示例:

import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs/Rx';

interface Hero {
    id: number;
    name: string;
}

@Component({
    selector: 'async-pipe-example',
    template: `
       

Async Pipe Example

Heroes:
  • {{ hero.name }}
Hero:

{{ (hero$ | async).id }} {{ (hero$ | async)?.name }}

` }) export class AsyncPipeComponent implements OnInit { heroes$: Observable; hero$: Observable; getHeroes(): Observable { return Observable.of([ { id: 1, name: 'Windstorm' }, { id: 13, name: 'Bombasto' }, { id: 15, name: 'Magneta' }, { id: 20, name: 'Tornado' } ]); } getHero(): Observable { return Observable.of({ id: 31, name: 'Semlinker' }); } ngOnInit() { setTimeout(() => { this.heroes$ = this.getHeroes(); this.hero$ = this.getHero(); }, 2000); } }

以上代码运行后浏览器显示的结果:

Angular 2 AsyncPipe_第5张图片

上面例子中有两个注意点:

1.使用 ngIf 控制 span 元素的显示:

 {{ (hero$ | async).id }}

2.使用 ?. 安全导航操作符,控制 name 属性的显示:

{{ (hero$ | async)?.name }}

若去掉 ngIf?. ,应用程序将会抛出异常,建议读者亲身体验一下。

我有话说

1.Promise vs Observable

  • Promise

    • 返回单个值

    • 不可取消的

  • Observable

    • 随着时间的推移发出多个值

    • 可以取消的

    • 支持 map、filter、reduce 等操作符

    • 延迟执行,当订阅的时候才会开始执行

详细内容可以参考 - RxJS 核心概念之Observable

2.使用 AsyncPipe 会发送多次请求

我们直接看一下具体示例:

app.component.ts

import { Component, OnInit } from '@angular/core';
import { Http } from '@angular/http';
import { Observable } from 'rxjs/Rx';

@Component({
  selector: 'exe-app',
  template: `
   

{{ (person$ | async)?.id }}

{{ (person$ | async)?.title }}

{{ (person$ | async)?.body }}

` }) export class AppComponent implements OnInit { person$: Observable<{ id: number; title: string; body: string }>; constructor(private http: Http) { } ngOnInit() { this.person$ = this.http.get('https://jsonplaceholder.typicode.com/posts/1') .map(res => res.json()) } }

以上代码运行后能正常显示结果,但如果你切换到开发者工具的网络面板,你会发现发送了三个重复的请求。这是因为我们的 Observable 是 cold 的,每处使用 async 管道的地方都会执行一次。针对上述问题,大部分人推荐的解决方案如下:

this.http.get('https://jsonplaceholder.typicode.com/posts/1')
     .map(res => res.json()).share()

我们只需使用 RxJS 中的共享操作符,它在内部调用 publish().refCount()。是不是很开心,但先等等,还有一种情况又会触发 HTTP 请求,具体我们再来看一下示例:

import { Component, OnInit } from '@angular/core';
import { Http } from '@angular/http';
import { Observable } from 'rxjs/Rx';

@Component({
  selector: 'exe-app',
  template: `
   

{{ (person$ | async)?.id }}

{{ (person$ | async)?.title }}

{{ (person$ | async)?.body }}

{{ (person$ | async)?.id }}

` }) export class AppComponent implements OnInit { person$: Observable<{ id: number; title: string; body: string }>; isShow: boolean = false; constructor(private http: Http) { } ngOnInit() { this.person$ = this.http.get('https://jsonplaceholder.typicode.com/posts/1') .map(res => res.json()) .share(); } showPersonInfo() { this.isShow = true; } }

以上代码运行后浏览器显示的结果:

Angular 2 AsyncPipe_第6张图片

我们发现当点击 '显示用户ID' 按钮时,又触发了新的请求。看来我们的救世主 - share() 不给力了,幸运的是我们还有新的救世主 - publishReplay() ,具体代码如下:

import { Component, OnInit } from '@angular/core';
import { Http } from '@angular/http';
import { Observable, ConnectableObservable } from 'rxjs/Rx';

@Component({
  selector: 'exe-app',
  template: `
   

{{ (person$ | async)?.id }}

{{ (person$ | async)?.title }}

{{ (person$ | async)?.body }}

{{ (person$ | async)?.id }}

` }) export class AppComponent implements OnInit { person$: ConnectableObservable<{ id: number; title: string; body: string }>; isShow: boolean = false; constructor(private http: Http) { this.preparePersonInfo(); } ngOnInit() { this.person$.connect(); } preparePersonInfo() { this.person$ = this.http.get('https://jsonplaceholder.typicode.com/posts/1') .map(res => res.json()) .publishReplay() } showPersonInfo() { this.isShow = true; } }

我们使用 publishReplay 替换了 share 操作符。调用 publishReplay() 方法后将返回一个ConnectableObservable 对象,当你调用 connect() 方法的时候,将主动执行订阅操作。

是不是感觉快奔溃了,就想简单的获取用户的信息,然后使用 AsyncPipe,怎么就那么多坑。。。

在大多数情况下,我们只需要从服务器获取数据并显示数据。如果只是这样的话,我们可以使用 Promise 来修复 AsyncPipe 发送多次 HTTP 请求的问题:

this.person = this.http.get("https://jsonplaceholder.typicode.com/posts/1")
 .map(res => res.json()).toPromise()

3.AsyncPipe 执行流程

Angular 2 AsyncPipe_第7张图片

接下来我们看一下 PromiseStrategy 与 ObservableStrategy 策略:

SubscriptionStrategy 接口

interface SubscriptionStrategy {
  createSubscription(async: any, updateLatestValue: any): any;
  dispose(subscription: any): void;
  onDestroy(subscription: any): void;
}

PromiseStrategy

class PromiseStrategy implements SubscriptionStrategy {
  createSubscription(async: Promise, 
    updateLatestValue: (v: any) => any): any {
      return async.then(updateLatestValue, e => { throw e; });
  }
  dispose(subscription: any): void {}
  onDestroy(subscription: any): void {}
}

ObservableStrategy

class ObservableStrategy implements SubscriptionStrategy {
  createSubscription(async: any, updateLatestValue: any): any {
    return async.subscribe({next: updateLatestValue, 
       error: (e: any) => { throw e; }});
  }
  dispose(subscription: any): void { subscription.unsubscribe(); }  // 取消订阅
  onDestroy(subscription: any): void { subscription.unsubscribe(); } 
}

总结

这篇文章我们介绍了 AsyncPipe 如何搭配 Promise 和 Observable 使用,此外介绍了 AsyncPipe 会发送多次请求的问题,最后我们得出的结论是如果只是需要从服务器获取数据并显示数据,可以使用 Promise 来修 AsyncPipe 发送多次 HTTP 请求的问题。

参考文章

  • Stop using observable when you should use a promise

  • Code VS Hot Observables

你可能感兴趣的:(typescript,angular2,angular.js)