EmotionTechテックブログ

株式会社エモーションテックのProduct Teamのメンバーが、日々の取り組みや技術的なことを発信していくブログです。

Angular の新しい制御フローにマイグレーションしてみた

はじめに

こんにちは。フロントエンドエンジニアのすずきです。

Angular v17 で developer preview として提供された組み込み制御フロー(built-in control flow)皆さんは試してみましたか?
本記事では、今までの構造ディレクティブ(*ngIf*ngFor など)から新しい制御構文に書き換えてみようと思います。

さっそく書き換えてみる

組み込み制御フローへのマイグレーションにはコマンドが用意されているので、このコマンドを使っていきます。

ng generate @angular/core:control-flow

実行環境は下記の通りです。

Angular CLI 17.1.2
Angular 17.1.2
Node 18.15.0

実行すると画像のようなログが表示されます。

マイグレーションコマンドの実行ログ

対象のディレクトリと、マイグレーション後にフォーマットするかを対話形式で聞いてくれます。
また、 developer preview なので、注意書きも表示されます。

一気に全部マイグレーションしてしまうと確認やテストが大変なこともあるので、少しずつ移行できるのは嬉しいですね。

マイグレーションした結果

if

-<ng-container *ngIf="isLoading; else list"> loading... </ng-container>
-<ng-template #list>
+@if (isLoading) {
+  loading...
+} @else {
   <app-todo-list [todoList]="todoList"></app-todo-list>
-</ng-template>
+}

*ngIf のための <ng-container>が消えているだけでなく、else で表示していた <ng-template> タグもちゃんと消えています。

for

<ul>
-  <li *ngFor="let todo of todoList; trackBy: trackByTodoList">
-    <app-todo-list-item
-      [title]="todo.title"
-      [status]="todo.status"
-    ></app-todo-list-item>
-  </li>
+  @for (todo of todoList; track trackByTodoList($index, todo)) {
+    <li>
+      <app-todo-list-item
+        [title]="todo.title"
+        [status]="todo.status"
+      ></app-todo-list-item>
+    </li>
+  }
 </ul>

こちらは <li> タグを残したままマイグレーションがされています。
元々指定していた trackBy 関数もそのまま使うように移行されていました。

@for では track の指定が必須になっています。
そのため、元のソースコードtrackBy を指定してない場合
<li *ngFor="let todo of todoList">

@for (todo of todoList; track todo)
のように track にループ変数が指定されます。

switch

-<ng-container [ngSwitch]="status"
-  ><span *ngSwitchCase="'NOT_STARTED'">未着手</span
-  ><span *ngSwitchCase="'IN_PROGRESS'">進行中</span
-  ><span *ngSwitchCase="'DONE'">完了</span></ng-container
->{{ title }}
+@switch (status) {
+  @case ('NOT_STARTED') {
+    <span>未着手</span>
+  }
+  @case ('IN_PROGRESS') {
+    <span>進行中</span>
+  }
+  @case ('DONE') {
+    <span>完了</span>
+  }
+}
+{{ title }}

*ngIf の時と同様に ngSwitch のための <ng-container>が消えています。
また、型が合わない case を書くとエラーにしてくれるようになりました。(下のコードの status の型は "NOT_STARTED" | "IN_PROGRESS" | "DONE" です。)

型が合わない case 節がエラーになる

構造ディレクティブを使っていたときは ngSwitchngSwitchCase が別ディレクティブとして提供されていたため、型の引き継ぎができずエラーになりませんでした。
マイグレーション後の方が型安全なコードが書けるようになっています。

ts ファイル

マイグレーションコマンドを実行すると ts ファイルにも変更が入りました。 これは構造ディレクティブを使わなくなり、CommomnModule を import する必要がなくなったためです。
import 不要になった全てのファイルに下記のような変更が入っていました。

-import { CommonModule } from '@angular/common';
+
 import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
 
 @Component({
   selector: 'app-todo-list-item',
   standalone: true,
-  imports: [CommonModule],
+  imports: [],
   templateUrl: './todo-list-item.component.html',
   styleUrl: './todo-list-item.component.scss',
   changeDetection: ChangeDetectionStrategy.OnPush,

全体を通して

JavaScript に近い構文になり、とても読みやすくなりました。 分岐や繰り返し処理はアプリケーションを作る上でほぼ必須となるため、 <ng-container> などの Angular 独自のタグや構造ディレクティブという概念を学ばずに書けると、 これから Angular を始める人のハードルも下がるのではと思います。

また、組み込み制御フローを使うと構造ディレクティブの import が不要になりバンドルサイズが削減されるので、開発者だけでなくユーザーにもメリットがあります。

本記事の中でご紹介したコードはこのブログ用のサンプルのため元々小さいのですが、0.93KB 削減されました。

マイグレーション

マイグレーション

試しに実際のプロダクトをマイグレーションしたところ 8.85KB 削減されました。

マイグレーション

マイグレーション

おわりに

いかがでしたか。

新しい制御フロー構文によって、より直感的で簡潔にコードが書けるようになったと思います。マイグレーションも想像以上に簡単で、スムーズに行えました。
これからもAngularの新機能を積極的に取り入れていきたいと思います。

エモーションテックでは顧客体験・従業員体験の改善をサポートし世の中の体験を変えるプロダクトを開発しております。もし弊社に興味を持って頂けましたら、ぜひ採用ページからご応募をお願いいたします。

hrmos.co