条件分岐や繰り返しを簡潔に記述できるテンプレートの新記法
これまでAngularで利用されていたngIfやngForといったディレクティブに代わって、Angular 17ではテンプレートで条件分岐や繰り返しをより簡潔に記述できる、新しい記法が導入されました。新旧記法を比べながら以下で説明していきます。
条件分岐1(if、else)
図3は、チェックボックスのチェック有無をもとに表示を切り替えるサンプルです。
この条件分岐を従来の記法で記述するとリスト1の通りになります。(1)の*ngIfディレクティブの設定で条件(isChecked)と、条件に合致しないときに表示する要素(2)の指定(else isNotChecked)を記述します。
<div *ngIf="isChecked; else isNotChecked"> <!--(1)--> 私のスマホはAndroidです。 </div> <ng-template #isNotChecked> <!--(2)--> <div>私のスマホはAndroidではありません。</div> </ng-template>
同じ内容を新しい記法で記述するとリスト2の通りになります。@ifと@elseで条件に合致したときとしないときの表示内容を記述します。
@if (isChecked) {
<div>私のスマホはAndroidです。</div>
} @else {
<div>私のスマホはAndroidではありません。</div>
}
条件分岐(switch、case)
図4は、テキストボックスの入力値から表示を切り替えるサンプルです。入力値が「Apple」なら「iPhone」、「Samsung」なら「Galaxy」、それ以外は「その他」と表示します。
この条件分岐を従来の記法で記述するとリスト3の通りになります。ngSwitchディレクティブに変数名(vendorName)を指定して、ngSwitchCaseディレクティブで変数の値に対応した表示を、ngSwitchDefaultディレクティブでデフォルトの表示を記述します。
<div [ngSwitch]="vendorName"> 私のスマホは: <span *ngSwitchCase="'Apple'">iPhone</span> <span *ngSwitchCase="'Samsung'">Galaxy</span> <span *ngSwitchDefault>その他</span> </div>
同じ内容を新しい記法で記述するとリスト4の通りになります。@switchに変数名(vendorName)を指定して、@caseで変数の値に対応した表示を、@defaultでデフォルトの表示を記述します。
<div>
私のスマホは:
@switch (vendorName) {
@case ('Apple') { <span>iPhone</span> }
@case ('Samsung') { <span>Galaxy</span> }
@default { <span>その他</span> }
}
</div>
繰り返し(for)
図5は、複数データのリストを繰り返し処理で表示するサンプルです。
複数データのリストは、app.component.tsに、リスト5の通り定義されています。Arrayの型として指定されているPhoneは、文字列型のvendor(=会社名)とname(=機種名)を含むインタフェースです(詳細はサンプルコード参照)。
phoneList: Array<Phone> = [
{
'vendor': 'Apple',
'name': 'iPhone 15'
},
(略)
]
リスト5のデータを表示する従来の記法はリスト6の通りです。
<div *ngFor="let phone of phoneList; trackBy: trackByMethod">
{{phone.vendor}}:{{phone.name}}
</div>
リスト6では、ngForディレクティブの「let phone of phoneList」記述により、phoneList配列の要素をphone変数として参照し、<div>タグ内で表示します。「trackBy」は、配列要素を一意識別する方法を定義するメソッド(trackByMethod)の指定で、このメソッドはapp.component.tsにリスト7の通り実装されています。このメソッドでphone.nameを返却することで、name(=機種名)で配列要素を一意識別することを表します。ngForディレクティブでtrackByの指定は必須ではありませんが、指定しないと配列の変更時に(変更された要素だけでなく)すべての要素を書き換えるため、パフォーマンスが低下します。
trackByMethod(index:number, phone:Phone) {
return phone.name;
}
一方、新しい記法はリスト8の通りです。@for内に「phone of phoneList」と記述して、リスト6同様にphoneList配列の要素をphoneとして参照します。「track phone.name」は、リスト6の「trackBy」に対応します。古い記法では一意識別に利用する要素を返却するメソッド名を指定しましたが、新しい記法では一意識別に利用する要素(ここではname)を直接指定できます。なお、古い記法と異なり、新しい記法では(パフォーマンスの低下を避けるため)trackの指定が必須となります。
@for (phone of phoneList; track phone.name) {
<div>{{phone.vendor}}:{{phone.name}}</div>
}
以上、Angular 17で導入されたテンプレートの新記法を説明しました。@if、@forなどの新記法は、従来の記法と比較して、JavaScriptや他のプログラム言語の記法に近いことから、より容易に習熟でき、また、簡潔に記述できます。
コンポーネントの遅延読込を簡潔に記述できる@defer
読込に時間がかかるコンポーネントを必要になるまで読み込まない遅延読込は、パフォーマンスの改善に効果的です。Angular 17では、コンポーネントの遅延読込を簡潔に記述できる「@defer」が導入されました。この利用法を、サンプルコード(p004-defer)で説明していきます。このサンプルは、遅延処理を含む3つのコンポーネントをルーターで切り替えるようになっています。遅延読込の効果を確認するには、各コンポーネントを表示後、Webブラウザーを再読込してください。
@deferの基本的な記述方法
もっとも単純なサンプル1(図6)では、Webブラウザーを再読込すると、大きな画像を含むコンポーネントをロード完了するまでの間、「ロード中...」と表示されます。
図6を表示するコンポーネント(Sample1Component)のテンプレートには、リスト9の通り記述します。
@defer () { <!--(1)-->
<app-pizza></app-pizza>
} @loading { <!--(2)-->
ロード中...
} @error { <!--(3)-->
ロードエラー
}
遅延読込させたい<app-pizza>コンポーネントを、(1)の通り@deferで囲んで記述します。(2)の@loadingにはロード中の、(3)の@errorにはロードエラー時の表示を記述します。この記述により、<app-pizza>コンポーネントが遅延読込され、読込完了するまでは@loadingに指定された「ロード中...」が表示されます。なお<app-pizza>コンポーネントは、読込に時間がかかるよう、テンプレート(pizza.component.html)内にサイズの大きな画像を埋め込んでいます(詳細はサンプルコードを参照)。
遅延読込のタイミングを制御する
@deferでは、遅延読込のタイミングを制御する仕組みが提供されています。サンプル2(図7)では、画面が表示されてから5秒後に遅延読込を開始します。
図7を表示するコンポーネント(Sample2Component)のテンプレートには、リスト10の通り記述します(@loading、@errorは省略)。@deferの引数「on timer(5s)」により、5秒後に遅延読込が開始されます。@placeholderには、読込開始前の表示を記述します。
@defer (on timer(5s)) {
<app-pizza></app-pizza>
} @placeholder {
(5秒後、ここにピザが表示されます)
}
サンプル3(図8)では、ボタンクリックで遅延読込を開始します。
図8を表示するコンポーネント(Sample3Component)のテンプレートには、リスト11の通り記述します(@loading、@errorは省略)。@deferの引数「when startDisplay」により、startDisplay変数がtrueになった時に遅延読込が開始されます。なおこのサンプルでは、startDisplayの初期値はfalseで、ボタンクリック時にtrueになるよう実装されています(詳細はサンプルコードを参照)。
@defer (when startDisplay) {
<app-pizza></app-pizza>
}
このように@deferでは、引数に「on」や「when」を指定して、遅延読込のタイミングを制御できます。詳細は公式ドキュメントも参照してください。
