Angular 2 AsyncPipe

更新時間 - 2017-03-21 15:02html

今天咱們來介紹一下 Angular 2 中 AsyncPipe (異步管道) ,使用 AsyncPipe 咱們能夠直接在模板中使用 PromiseObservable 對象,而不用經過定義一個類的成員屬性來存儲返回的結果。typescript

AsyncPipe 訂閱一個 Observable 或 Promise 對象,並返回它發出的最新值。 當發出新值時,異步管道會主動調用變化檢測器的 markForCheck() 方法,標識組件需執行變化檢測。 當組件被銷燬時,異步管道自動取消訂閱,以免潛在的內存泄漏。json

AsyncPipe with Promise

Promise 未使用 AsyncPipesegmentfault

promise.component.tspromise

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

@Component({
    selector: 'exe-promise',
    template: `
     <h4>Promise Component</h4>
     <p>{{promiseData}}</p>
    `
})
export class PromiseComponent {
    promiseData: string;

    constructor() {
        this.getPromise().then(v => this.promiseData = v);
    }

    getPromise(): Promise<string> {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve('Promise complete!');
            }, 2000);
        });
    }
}

app.component.ts瀏覽器

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

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

以上代碼運行後瀏覽器顯示的結果:安全

圖片描述

Promise 使用 AsyncPipe服務器

promise-async-pipe.component.ts網絡

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

@Component({
    selector: 'exe-promise-pipe',
    template: `
     <h4>Promise with AsyncPipeComponent</h4>
     <p>{{ promise | async }}</p>
    `
})
export class PromiseAsyncPipeComponent {
    promise: Promise<string>;
    constructor() {
       this.promise = this.getPromise();
    }

    getPromise(): Promise<string> {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve('Promise with AsyncPipe complete!');
            }, 2000);
        });
    }
}

app.component.tsapp

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

@Component({
  selector: 'exe-app',
  template: `
   <exe-promise-pipe></exe-promise-pipe>
  `
})
export class AppComponent { }

以上代碼運行後瀏覽器顯示的結果:

圖片描述

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: `
     <h4>Observable Component</h4>
     <p>{{ observableData }}</p>
    `
})
export class ObservableComponent implements OnDestroy {
    observableData: number;
    subscription: Subscription = null;

    constructor() {
        this.subscribeObservable();
    }

    getObservable(): Observable<number> {
        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: `
   <exe-observable></exe-observable>
  `
})
export class AppComponent { }

以上代碼運行後瀏覽器顯示的結果:

圖片描述

Observable 使用 AsyncPipe

observable-async-pipe.component.ts

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

@Component({
    selector: 'exe-observable-pipe',
    template: `
     <h4>Observable with AsyncPipe Component</h4>
     <p>{{ observable | async }}</p>
    `
})
export class ObservableAsyncPipeComponent {
    observable: Observable<number>

    constructor() {
        this.observable = this.getObservable();
    }

    getObservable(): Observable<number> {
        return Observable
            .interval(1000)
            .take(10)
            .map(v => v * v);
    }
}

app.component.ts

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

@Component({
  selector: 'exe-app',
  template: `
   <exe-observable-pipe></exe-observable-pipe>
  `
})
export class AppComponent { }

以上代碼運行後瀏覽器顯示的結果:

圖片描述

爲了更讓讀者更好地掌握 AsyncPipe, 咱們再來一個示例:

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

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

@Component({
    selector: 'async-pipe-example',
    template: `
       <h4>Async Pipe Example</h4>
       <div>
          <h5>Heroes: </h5>
          <ul>
            <li *ngFor="let hero of heroes$ | async">
                {{ hero.name }}
            </li>
          </ul> 
          <h5>Hero: </h5>
          <p> 
            <span *ngIf="hero$">{{ (hero$ | async).id }}</span>
            <span>{{ (hero$ | async)?.name }}</span>
          </p>
       </div>
    `
})
export class AsyncPipeComponent implements OnInit {
    heroes$: Observable<Hero[]>;
    hero$: Observable<Hero>;

    getHeroes(): Observable<Hero[]> {
        return Observable.of([
            { id: 1, name: 'Windstorm' },
            { id: 13, name: 'Bombasto' },
            { id: 15, name: 'Magneta' },
            { id: 20, name: 'Tornado' }
        ]);
    }

    getHero(): Observable<Hero> {
        return Observable.of({
            id: 31, name: 'Semlinker'
        });
    }

    ngOnInit() {
        setTimeout(() => {
            this.heroes$ = this.getHeroes();
            this.hero$ = this.getHero();
        }, 2000);
    }
}

以上代碼運行後瀏覽器顯示的結果:

圖片描述

上面例子中有兩個注意點:

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

<span *ngIf="hero$">{{ (hero$ | async).id }}</span>

2.使用 ?. 安全導航操做符,控制 name 屬性的顯示:

<span>{{ (hero$ | async)?.name }}</span>

若去掉 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: `
   <div>
      <p>{{ (person$ | async)?.id }}</p>
      <p>{{ (person$ | async)?.title }}</p>
      <p>{{ (person$ | async)?.body }}</p>
    </div>
  `
})
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: `
   <div>
      <p>{{ (person$ | async)?.id }}</p>
      <p>{{ (person$ | async)?.title }}</p>
      <p>{{ (person$ | async)?.body }}</p>
      <button (click)="showPersonInfo()">顯示用戶ID</button>
      <p *ngIf="isShow">{{ (person$ | async)?.id }}</p>
    </div>
  `
})
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;
  }
}

以上代碼運行後瀏覽器顯示的結果:

圖片描述

咱們發現當點擊 '顯示用戶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: `
   <div>
      <p>{{ (person$ | async)?.id }}</p>
      <p>{{ (person$ | async)?.title }}</p>
      <p>{{ (person$ | async)?.body }}</p>
      <button (click)="showPersonInfo()">顯示用戶ID</button>
      <p *ngIf="isShow">{{ (person$ | async)?.id }}</p>
    </div>
  `
})
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 執行流程

圖片描述

接下來咱們看一下 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<any>, 
    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 請求的問題。

參考文章

相關文章
相關標籤/搜索