Carpe Diem

備忘録。https://github.com/jun06t

AngularでAsync pipeを使う

概要

AngularのPipeの中にはAync PipeというPromiseやObservableな非同期オブジェクトをそのままtemplateで表示できるPipeがあります。 今回はその使い方を紹介します。

環境

  • angular 2.4.8
  • rxjs 5.2.0

async pipeのメリット

主なメリットは以下の2つです。

フロントではメモリ管理が大切なので、特に後者は嬉しいメリットですね。

基本的な使い方

@Component({
  selector: 'async-pipe',
  template: '<div>Time: {{ date | async }}</div>'
})
export class AsyncPipeComponent {
  public date: Observable<Date> = Observable.create(
    observer => {
      const now = Date.now();
      observer.next(now);
      observer.complete();
    });
}

https://embed.plnkr.co/AJzEjrWzfxq7i2PF7sqH/

結果

Time: 1489196337153

PromiseObservableなオブジェクトをtemplate側で| asyncと一緒に置くことで、その時のObservable<T>T型のオブジェクトを表示することができます。

特定のフィールドを表示したい場合

複数のメンバを持ったUserオブジェクトを渡したい、ということもあると思います。

class User {
  name: string;
  age: number;
}

@Component({
  selector: 'async-pipe',
  template: '<div>User: {{ (user | async)?.name }}</div>'
})
export class AsyncPipeComponent {
  public user: Observable<User> = Observable.create(
    observer => {
      const user: User = {
        name: 'Tom',
        age: 20
      };
      observer.next(user);
      observer.complete();
    });
}

https://embed.plnkr.co/oeMEsuZ1LN7u5egnw9cC/

結果

User: Tom

このように()でくくると、オブジェクトとして扱うことができます。

複数のasync pipeがある時

templateに複数のasync pipeがある場合、Cold Observableだとその数の分callされてしまいます。

NG

@Component({
  selector: 'async-pipe',
  template: `
  <div>Time1: {{ date | async }}</div>
  <div>Time2: {{ date | async }}</div>
  `
})
export class AsyncPipeComponent {
  public date: Observable<Date> = Observable.create(
    observer => {
      console.log('called');
      const now = Date.now();
      observer.next(now);
      observer.complete();
    });
}

https://embed.plnkr.co/o3zx1xLOqbXAx7e4yJFM/

結果

// called
// called
Time1: 1489196337153
Time2: 1489196337154

同じオブジェクトなのにわざわざ2回呼ぶことになってしまい、無駄な処理となります。
これはHot Observableに変換することで、1度だけにすることができます。

OK

@Component({
  selector: 'async-pipe',
  template: `
  <div>Time1: {{ date | async }}</div>
  <div>Time2: {{ date | async }}</div>
  `
})
export class AsyncPipeComponent {
  public date: Observable<Date> = Observable.create(
    observer => {
      console.log('called');
      const now = Date.now();
      observer.next(now);
      observer.complete();
    })
    .share();
}

https://embed.plnkr.co/wEy7SdCUoakAdk8WDRWv/
※plunkerではきちんと表示されませんが、通常のangular2では表示されます

結果

// called
Time1: 1489196337153
Time2: 1489196337153

@Inputにasync pipeをセットする場合

コンポーネントの@Inputにasync pipeを設定する場合、@Inputにはまずnullが渡されてしまうので、ちゃんとrenderされないです。

NG

Parent Component

@Component({
  selector: 'async-pipe',
  template: '<pipe-child [text]="(date | async)"></pipe-child>'
})
export class AsyncPipeComponent {
  public date: Observable<Date> = Observable.create(
    observer => {
      const now = Date.now();
      observer.next(now);
      observer.complete();
    });
}

Child Component

@Component({
  selector: 'pipe-child',
  template: '<span>Time: {{text}}</span>'
})
export class AsyncPipeComponent {
  @Input text: Date;
}

結果

Time: null

OK

Parent Component

Parent Componentはそのままで大丈夫です。

Child Component

@Component({
  selector: 'pipe-child',
  template: '<span>Time: {{internalText}}</span>'
})
export class AsyncPipeComponent implements OnChanges {
  @Input text: Date;
  public internalText: Date;

  ngOnChanges(changes: any) {
    if (changes.text) {
      this.internalText = changes.text.currentValue;
    }
  }
}

このようにngOnChangesを使って、後から変更するように修正します。
結果

TIme: 1489196337153

ちゃんと表示されるようになりました。

まとめ

Async Pipeの使い方を紹介しました。
そのViewで一度しか使わないようなObservableな値は、async pipeを使ってtemplate側に書いた方がコードがシンプルになります。
一方で「ボタンを押したら状態が更新される」ようなケースでは、component側でsubscribeした方が良いです。

ソース