Marginalia

[Angular 4.0] 新しいngIfの使い方

Angular 4.0にはいくつかの新しい機能が追加されます。 今回はngIfに追加される新しい機能について解説します。

ngIfの新機能

ngIfThen: テンプレート分離

これまでのngIfで表示を切り替えられるのは、ngIfディレクティブが付与された要素とその内側の要素だけでした。 Angular 4.0以降は、ngIfによる条件付けと、その条件により制御されるテンプレートを分離できます。 次の例では、showプロパティが真のときに#thenBlockのテンプレートが描画されます。

<div *ngIf="show; then thenBlock">ignored</div><ng-template #thenBlock>show === true</ng-template>

thenテンプレートにはTemplateRefのインスタンスを渡せます。nullのときは無視されます。 次の例では、条件によってngIfによって描画されるテンプレートを切り替えています。 それほどユースケースは多くないですが、必要になる場面があるかもしれません。

@Component({
  selector: 'ng-if-then',
  template: `
    <button (click)="switchPrimary()">Switch Template</button>

    <div *ngIf="show; then thenBlock"></div>
    <ng-template #primaryBlock>Primary</ng-template>
    <ng-template #secondaryBlock>Secondary</ng-template>
`
})
class NgIfThenElse implements OnInit {
  thenBlock: TemplateRef<any> = null;
  show: boolean = true;

  @ViewChild('primaryBlock')
  primaryBlock: TemplateRef<any> = null;
  @ViewChild('secondaryBlock')
  secondaryBlock: TemplateRef<any> = null;

  switchPrimary() {
    this.thenBlock = this.thenBlock === this.primaryBlock ? this.secondaryBlock : this.primaryBlock;
  }

  ngOnInit() { this.thenBlock = this.primaryBlock; }
}

ngIfElse: 偽のときのテンプレート

先ほどのthenテンプレートはこのelseテンプレートの副産物とも言えるでしょう。 その名前のとおり、ngIfに渡された式が偽のときに描画されるテンプレートを指定する仕組みです。 elseテンプレートは次のように使います。

<div *ngIf="show; else elseBlock">show === true</div>
<ng-template #elseBlock>show === false</ng-template>

thenテンプレートとelseテンプレートは併用できます。

<div *ngIf="show; then thenBlock; else elseBlock"></div>
<ng-template #thenBlock>show === true</ng-template>
<ng-template #elseBlock>show === false</ng-template>

これまでは真の場合と偽の場合にそれぞれ逆の条件のngIfが必要でしたが、簡単に書けるようになります。

As構文: 評価結果の変数化

これこそがngIf最大の変更点です。 ngIfに渡された式の評価結果をローカル変数にアサインできるようになりました。 これはasyncパイプとngIfを併用するケースで絶大な効果を発揮します。 次の例を見てみましょう。 非同期にユーザー情報が得られるコンポーネントで、データ取得後に.nameプロパティを表示したいとき、 これまではこのようにasyncパイプと?.構文を組み合わせていました。

<p>{{ (user$ | async)?.name }}</p>

これが.name以外にも.age.iconなども使いたいとなると、テンプレートは大変なことになります。

<p>{{ (user$ | async)?.name }}</p>
<p>{{ (user$ | async)?.age }}</p>
<img [src]="(user$ | async)?.icon">

せめて?.をなくそうとngIfで囲っても、asyncパイプは全てのバインディングに必要です。

<div *ngIf="user$ | async">
    <p>{{ (user$ | async).name }}</p>
    <p>{{ (user$ | async).age }}</p>
    <img [src]="(user$ | async).icon">
</div>

Angular 4.0以降は、asを使うことで評価結果を変数として保持できます。つまり、user$ | asyncの結果であるユーザーデータを同期的に扱えるようになります。具体的には、次のように書けます。

<div *ngIf="user$ | async as user">
    <p>{{ user.name }}</p>
    <p>{{ user.age }}</p>
    <img [src]="user.icon">
</div>

Woohoo!!!!! :tada::tada::tada::tada::tada:

この変化は単にテンプレートがきれいになるだけでなく、asyncパイプが減ることによるパフォーマンスの改善も得られます。 アプリケーションをObservableベースのリアクティブな設計したときも、テンプレートが自然に書けるようになります。

elseテンプレートと併用すれば、今まではとても複雑になっていたテンプレートが次のようにスッキリします。

<div *ngIf="user$ | async as user; else userNotFound">
    <p>{{ user.name }}</p>
    <p>{{ user.age }}</p>
    <img [src]="user.icon">
</div>
<ng-template #userNotFound>
    <p>not found</p>
</ng-template>

このas構文はngForでも使用できます。

<div *ngFor="let user of (users$ | async) as users; index as i">
    <span>{{ i + 1 }} / users.length</span>
    <span>{{ user.name }}</span>
</div>

まとめ

  • thenテンプレートでテンプレートの分離と切り替えが可能になる
  • elseテンプレートで偽のときのテンプレートを指定できる
  • asによる変数化でasyncパイプとの親和性が改善される

Angular 4.0 Features