😎

Angular18登場!zoneless 変更検知が初公開されました

2024/07/08に公開


Angularのバージョン18が出ました!👏👏👏以前導入された新機能やAPIを更に安定させる一方、多くの開発者が願ったzoneless change detectionにもう一歩踏み出せるようになりました。

まず、Angularの公式ドキュメントサイトが正式にangualr.devに変わったとのことです。 今までの公式ドキュメントとして頑張ってきたangular.ioはもうアクセスできなくなり、angular.devにリダイレクトされます。
playgroundが用意されており、別に環境構築をしなくてもAngularの機能を触れますので、色々試してみてください!

では、バージョン18は何が変わったのか確認してみましょう。

変更検知(change detection)の進化

今までAngularの変更検知はzone.jsというライブラリーに依存してきました。zone.jsライブラリーはパフォーマンス的に弱点があり、Angularチームはこの数年間zone.jsに依存しない方法を工夫してきました。ついにAngular18でzoneless change detectionの実験的なAPIを初公開しました!

bootstrapApplication(App, {
  providers: [
    provideExperimentalZonelessChangeDetection()
  ]
});
polyfills.ts
- zone.js

上記のようにアプリのブートストラップのprovidersオプションに追加すると、新しいzoneless変更検知が使えます。 polyfillsに記載しているzone.jsを削除するのもお忘れないでくださいね!
ちなみに、ChangeDetectionStrategy.OnPushにより、変更検知ができるコンポーネントならば、スムーズにzonelessへの変換ができるそうです。

zoneなしの変更検知(zoneless change detetion)により、以下のようなメリットが期待できるとのことです。

  • パフォーマンスの向上: zone.jsはアプリケーションのビューの変更を検知するため、DOMイベントや非同期タスクをその指標とみなし、アプリケーション全体の同期処理を頻繁に実行します。ただし、その指標が必ずしも実際の更新状態を示すわけではないので、必要以上に同期処理を行うのが事実です。zonelessになると、この不要な同期処理がなくなり、パフォーマンス向上にもつながります。
  • コアウェブバイタル(Core Web Vitals)の改善:zone.jsはペイロードサイズと起動時間に結構オーバーヘッドをもたらします。zonelessはこういうオーバーヘッドをなくし、コアウェブバイタルのスコア改善にも繋がります。
  • デバックが簡単になる:スタックトレース(stack trace)がより読みやすくなり、zoneの領域外でバグも探しやすくなります。
  • ソフトウェアのエコシステムとの相互運用性を向上: zone.jsはブラウザAPIをパッチすることで動作しますが、新しいブラウザAPIの全てを自動的にパッチすることではありません。async/awiatなど、一部のAPIは効果的にパッチしづらいがめ、zone.jsを動作させるためには古い形式に交換する必要があります。エコシステムのライブラリーの中ではzone.jsのパッチとの交換性を持たないものもあります。zone.jsの依存性をなくすと、こういう問題が解消でき、パッチによる複雑さをなくし、持続的なメインテナンスが可能となります。

どうしてもzone.jsはAngularに欠かせないものでしたので、今までパフォーマンスのチューニングやデバックするのに結構苦労しましたが、zoneless change detectionが一般的になると、より気軽にアプリの改善ができそうですね!

zonelessとsignalの組み合わせが望ましい

zonelssを効果的に使用するためにはsignalと一緒に使ったほうが良いでしょう。

@Component({
  ...
  template: `
    <h1>Hello from {{ name() }}!</h1>
    <button (click)="handleClick()">Go Zoneless</button>
  `,
})
export class App {
  protected name = signal('Angular');

  handleClick() {
    this.name.set('Zoneless Angular');
  }
}

上記の例では、ボタンをクリックするとhandleClickメソッドを呼び出し、signalの値を更新し、UIが更新されます。動き自体はzone.jsを使った時と似ていますが、いくつか違うところがあります。zone.jsによる変更検知(change detection)はアプリの状態が少しでも変化がある度に行われますが、zoneがない(zoneless)時はsiganlの更新など、制限されたトリガーをチェックすることで、変更検知をより効率的に行います。また、何度も連続的に変更検知するのを防ぐため、今回の変更には新しいスケジューラーが結合されたとのことです。
zonelessに対してもっと深く知りたい方はこちらの公式ドキュメントを参考にしてください。

コアレシング(coalescing)がディフォルトな動き

v18からはゾーンなし(zoneless)のアプリもzone.jsを利用するアプリも、コアレシング(coalescing)を有効にした共通のスケジューラを使用します。新しいzone.jsアプリにおける変更検知サイクル数を削減するため、ゾーンコアレシングもデフォルトで有効化するようになりました。コアレシングは、不要な変更検知サイクルを削減することで、一部のアプリケーションのパフォーマンス向上が期待されます。
既存のプロジェクトにコアレシングを有効化するためには以下のコードのように、providerの設定を変更してください。

bootstrapApplication(App, {
  providers: [
    provideZoneChangeDetection({ eventCoalescing: true })
  ]
});

ネイティブな async/await をゾーンなし(zoneless)アプリで利用

Zone.jsはAngularの変更検知を取り組むために、多くのブラウザーコールをインターセプトします。しかし、非同期/待機 (async/await) はzone.jsがうまくパッチできないAPIの一つであり、Angular CLIを通じてはPromiseとしてダウングレードする必要がありました。これはあまり好ましくありません。なぜなら、async/awaitは全てのモダンブラウザーががサポートしており、Promiseに比べても表現力が高く、Javascriptランタイムによって最適化されているためです。

ゾーンなしの変更検知(zoneless change detection)はネイティブのasync/awaitを使えますので、promiseにダウングレードする必要なく、バンドルサイズも軽く維持し、デバックもより楽にしてくれるでしょう。

Signal APIは開発者向けプレビュー中

Angular 17.1と17.2バージョンでsignal inputquery signal,signal outputなどの機能が発表されました。こういうあらゆるなsignal APIに関しては現在、開発者向けのプレビュー中なので、安定版(stable mode)が出るまで積極的にフィードバックを反映してくれるそうです。ご参考までにー。

これらが安定版(stable mode)になりました!

Material 3

angulr material 3が安定版になったとのことです。ご興味のある方はこちらのAngular Material 3のドキュメントで確認してください。

Deferrable views

ビューの遅延表示ができるdeferrable viewも安定版になり、アプリケーションまたライブラリーで使えます。`@defer`を利用して、バンドルサイズが半分までに減らせたという経験も共有されたようで、これからも積極的に活用していきたいですね!

Built-in control flow

ディファラブル ビュー(Deferrable view)だけでなく、built-in control flow(ビルトインコントロールフロー)の方も安定版になったとのことです。プレビューの後、コントロールフローの型チェックを強化され、より人間工学に基づいた暗黙的な変数エイリアスも有効化され、特定のパフォーマンス関連のアンチパターンに対してはガードレールを設定したらしいです。

サーバーサイドレンダーリング(Server Side Rendering, SSR)の改善

国際化(i18n)ブロックのハイドレーションが利用可能に!

v17ではSSRにハイドレーション(hydration)のコンセプトを紹介し、安定版になりました。Angularv17のHTTPアーカイブデーターセットによると、76%のAngular17アプリがハイドレーションの戦略を採択し、プリレンダーリング(prerendering)やサーバーサイドレンダリング(SSR)を使っているそうです。

今まで、ハイドレーションの問題点が一つ残っていましたが、国際化(i18n)のサポート不足でした。AngularチームはChrome Auroraチームを連携し、v18のプレビュー版ではi18nのブロックのハイドレーション機能が利用可能になったとのことです!

CDK、Materialもハイドレーションサポート

v17では一部のAngular MaterialやCDKコンポーネントがハイドレーションの対象から除いたため、再レンダリングが発生しました。v18からは全てのコンポーネントとプリミティブがハイドレーションの対象になり、再レンダリングの現象も解除され、ハイドレーションとの互換性を持つようになりました。

部分的ハイドレーション(partial hydration)の計画

Angularチームはng-confというカンファレンスとGoogleI/Oで部分的ハイドレーション(partial hydration)という概念を発表しました。これはサーバーサイドレンダリングの後、段階的にハイドレーションを適用する技術であり、こういう段階的なハイドレーションを適用することで、最初にロードするJavaScriptの量を削減し、アプリケーションのパフォーマンスを向上させることができるようです。

部分的ハイドレーション(partial hydration)は、遅延ビュー(deferrable view)と同じ基盤の上に構築されます。 現在の仕組みでは@placeholderブロックはサーバー側でレンダリングされますが、部分的ハイドレーションなら、@deferブロックのメインコンテンツをサーバー側でレンダリングすることができます。 テンプレート上で指定されたトリガー条件が満たされた時に限って@deferブロックをハイドレーションし、関連しているJavascriptをダウンロードする動きになります。
以下のような感じで使えるらしいです。

@defer (render on server; on viewport) {
  <app-calendar/>
}

上記のコードブロックにより、カレンダーコンポーネントはサーバー側でレンダーされます。クライアント側になってビューポートに入った時に該当のJavascriptをダウンロードし、カレンダーがインテラクティブになります。
部分的ハイドレーション(partial hydration)は今のところ、プロトタイプの段階とのことです。将来に正式に導入されると更に進化したハイドレーションができそうですね!お楽しみです。

ng-contentにディフォルトコンテンツの設定が可能になりました。

ng-contentにデフォルトコンテンツを指定する機能は、長年多くの要望があり、ついにv18で実装可能になりました。使い例は以下のようです。

@Component({
  selector: 'app-product-list',
  template: `
    <app-product>
          <p>今ならお得!</p>
          <p class="head">ファッション</p>
    </app-product>
  `,
})

@Component({
  selector: 'app-product',
  template: `
    <p>カテゴリ</p>
    <ng-content selector="head"></ng-content>
    <ng-content></ng-content>
  `,
})

上記のように設定すると、以下のようにレンダーリングされます。

<!-- <app-product>でレンダリングされる内容-->
    <p>カテゴリ</p>
    <p>ファッション</p>
    <p>今ならお得!</p>

フォームコントロールの状態変化(state change)イベントの統合

AngularフォームのFormControlFormGroupFormArrayクラスに新たなプロパティーeventsが導入されました。こちらのeventsプロパティーはフォームコントロールの状態変化のイベントを購読(サブスクライブ、subscribe)して、コントロールの値(value)、タッチ状態(touch state)、初期状態(pristine state)などのステータスを監視するのができるようになりました。書き方は以下のようです。

const nameControl = new FormControl<string|null>('name', Validators.required);

nameControl.events.subscribe(event => {
  // それぞれのイベントの状態を確認
});

アプリケーションビルダー(application builder)への自動マイグレーション

v17でアプリケーションビルダー(application builder)を安定版として発表し、新規プロジェクトに対してはディフォルトに設定するようになっています。このビルダーは従来のwebpackを置き換えるため、viteesbuildを使っており、angular.jsonを更新することで、新しいビルドシステムへ移行ができました。

v18では誰もが新しいビルド環境に移行できるよう、自動マイグレーションの機能が導入されました。既存プロジェクトをng updateにより、アップデートすると、新しいビルドシステムへ移行するか確認の上、自動的にマイグレーションを行います。こちらのマイグレーションはあくまでオプションで、以下のコマンドにより移行もできます。

ng update @angular/cli --name use-application-builder

マイグレーションに対してもっと詳しく知りたい方はこちらの公式ガイドをご参考にしてください。

ルート(route)のリダイレクトを関数で制御

v18からより柔軟なリダイレクト処理を実現するためにルートのredirectToプロパティーに関数が受け入れられるようになりました。こちらの関数ではリダイレクト先のURLを文字列として返せます。書き方は以下のようです。

const routes: Routes = [
  { path: "first-component", component: FirstComponent },
  {
    path: "old-user-page",
    redirectTo: ({ queryParams }) => {
      const errorHandler = inject(ErrorHandler);
      const userIdParam = queryParams['userId'];
      if (userIdParam !== undefined) {
        return `/user/${userIdParam}`;
      } else {
        errorHandler.handleError(new Error('Attempted navigation to user page without user ID.'));
        return `/not-found`;
      }
    },
  },
  { path: "user/:userId", component: OtherComponent },
];

従来のredirectToプロパティは静的な文字列のみを受け入れていましたが、v18からはリダイレクト先を動的に制御できるようになりましたので、より多様なケースに対応できそうですね!

最後に

Angular v18で色々な改善点が出ましたが、その中でもやはり一番大きな変化と言えば、ゾーンなしの変更検知(zoneless change detection)のことですよね!zonelessの効果はどれぐらいかワクワクしています😊
新しいビルドシステムへのマイグレーションも楽になり、Angularアプリケーションは重いというイメージはもう近いうちになくなるかもしれませんね。皆さんもぜひAngular v18試してみたらどうですか?
ここまで読んでいただき、ありがとうございます。

脚注
  1. 参考資料:
    https://blog.angular.dev/angular-v18-is-now-available-e79d5ac0affe
    https://angular.dev/guide/experimental/zoneless
    https://angular.dev/tools/cli/build-system-migration ↩︎

Discussion