lacolaco's marginalia

Angular v22: ChangeDetectionStrategy.Eager の導入とOnPush By Default

Angular v21.2では、コンポーネントの変更検知戦略の選択肢として ChangeDetectionStrategy.Eager が新たに追加された。とはいえ、これは今までの ChangeDetectionStrategy.Default とまったく同じ挙動であり、エイリアスが追加されただけだ。

v21時点での変更検知戦略はEager (= Default)と OnPushが選択できるが、v22ではついにOnPushがデフォルトとなる。既存プロジェクトの移行パスについても計画が見えているので解説しておく。

Default-to-Eager Migration

Angular v22へのng updateマイグレーションによって、変更検知戦略が無指定、あるいはDefaultを指定しているコンポーネントでは、自動的にEagerに書き換えられる。これにより、既存プロジェクトでは従来通りの変更検知でアプリケーションが動作するため、破壊的な変更は起こらない。

OnPush By Default

v22から、ng newng generateで生成されるコンポーネントの変更検知戦略がデフォルトでOnPushとなる。もちろん設定ファイルで明示的にコンポーネントの生成オプションを changeDetection: Eager としていればそちらが適用されるが、そうでない場合は新規コードが何もしなくてもOnPushモードで動作することになる。

Eager-to-OnPush Migration?

今のところ、Eagerモードの廃止についての議論はない。OnPushのほうがパフォーマンスの優位性はあるが、焦って既存コードを移行する必要はないだろう。移行を進めるにあたっては、先にコンポーネントの内部状態をSignalに移行するとうまくいきやすい。OnPushにしてコンポーネントが動かなくなる一番の要因は、コンポーネントのクラスフィールドを内部で書き換えているケースだが、フィールドがSignalになっていればテンプレートが自動的にSignalの変更を購読してくれる。

@Component({
  template: `
-   <div>{{ userData?.name }}</div>
+   <div>{{ userData()?.name }}</div>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class Component {
   private userService = inject(UserService);

-  userData?: UserData | undefined;
+  readonly userData = signal<UserData | undefined>(undefined);

loadUserData() {
    this.userService.getUserData().then((data) => {
-      this.userData = data;
+      this.userData.set(data);
    });
  }
}

そもそもなぜAngularチームがOnPushをこれまで以上に推進したいのかというと、既存プロジェクトのZoneless化を促したいからだ。Zone.jsへの依存を剥がすためにはOnPush化が推奨される。必須ではないが、完全にOnPushで動いているならZone.jsは必要ないと保証できる。逆に言えば、Eagerモードで動いているコンポーネントがある場合は、Zone.jsへの依存の可能性が捨てきれない。

Angular MCP Serverでも onpush_zoneless_migration というツールを提供し、AIエージェントによるOnPush移行とZoneless化を支援している。

既存コードがそのままで動くとはいえ、今後のAngularエコシステムは「もうOnPushがデフォルトだよね」という空気で発展していくはずだ。その恩恵を漏らさず受け取りたいならば、やはりOnPush移行、Zoneless移行は避けて通れない道だろう。

まとめ

  • Angular v22 では ChangeDetectionStrategy.EagerDefault の後継として扱われる
  • 既存コードは ng updateDefault / 無指定のコンポーネントが自動的に Eager に移行され、振る舞いが保たれる
  • 新規作成されるコンポーネントは OnPush がデフォルトになる
  • すぐに既存の Eager を移行する必要はないが、今後は OnPush 前提のエコシステムに変化していくだろう