Angular 19 Zoneless:Zone.jsなしのパフォーマンスと変更検知
Angular 19とAngular 20のZoneless変更検知により、Zone.jsを完全に削除し、バンドルサイズを33KB削減、レンダリング速度を30-40%向上させます。provideExperimentalZonelessChangeDetectionからprovideZonelessChangeDetectionへの移行パス、シグナルベースの反応性、既存アプリケーションの移行における落とし穴、SSR対応、パフォーマンスベンチマークを詳しく解説します。

Angular zoneless変更検知は、スタンドアロンコンポーネント導入以来、フレームワークにおける最も重要なアーキテクチャの変革です。Zone.jsを完全に削除することで、Angularアプリケーションは約33KBの小さなバンドル、30〜40%削減された不要な変更検知サイクル、Zone特有のノイズのないクリーンなスタックトレースを実現します。
Angular 18でzonelessが実験的に導入されました。Angular 19ではprovideExperimentalZonelessChangeDetection()を使用した実験的APIが洗練されました。Angular 20ではprovideZonelessChangeDetection()として安定版に昇格しました。Angular 21では新規プロジェクトのデフォルトになります。
Zone.js変更検知の内部動作
zonelessを理解する前に、Zone.jsのメカニズムを明確に説明する必要があります。Zone.jsはすべての非同期ブラウザAPIをモンキーパッチします:setTimeout、setInterval、Promise.then、addEventListener、XMLHttpRequestなど、数十のAPIです。これらのAPIのいずれかが完了するたびに、Zone.jsはAngularに通知し、Angularはコンポーネントツリー全体で変更検知を実行します。
このアプローチには根本的な欠陥があります。Zone.jsはアプリケーションの状態が実際に変更されたかどうかを知ることができません。アニメーションタイミングのためだけに使用されるsetTimeoutでも、完全な変更検知サイクルをトリガーします。数百のコンポーネントを持つ大規模なアプリケーションでは、このオーバーヘッドは測定可能になります。
import { ApplicationConfig } from '@angular/core';
import { provideZoneChangeDetection } from '@angular/core';
export const appConfig: ApplicationConfig = {
providers: [
provideZoneChangeDetection({ eventCoalescing: true }),
// Zone.jsは約130以上のブラウザAPIにパッチを当てる
// すべての非同期コールバックが変更検知をトリガーする
]
};イベント合体(Angular 14で導入)は、複数のイベントを単一の変更検知サイクルにバッチ処理することでオーバーヘッドを削減しますが、核心的な問題は残ります:変更検知は必要以上に頻繁に実行されます。
Angular 19と20でZoneless変更検知を有効にする
移行パスはAngularのバージョンによって異なります。Angular 19では実験的APIを使用し、Angular 20では安定版を提供します。
import { ApplicationConfig } from '@angular/core';
import { provideExperimentalZonelessChangeDetection } from '@angular/core';
export const appConfig: ApplicationConfig = {
providers: [
provideExperimentalZonelessChangeDetection(),
// Zone.jsパッチングなし
]
};import { ApplicationConfig } from '@angular/core';
import { provideZonelessChangeDetection } from '@angular/core';
export const appConfig: ApplicationConfig = {
providers: [
provideZonelessChangeDetection(),
]
};プロバイダーを切り替えた後、angular.jsonのビルドとテストターゲットの両方でpolyfills配列からzone.jsを削除し、パッケージをアンインストールします。
# angular.jsonのビルドとテストターゲットからzone.jsポリフィルを削除
# その後、パッケージをアンインストール
npm uninstall zone.jsバンドルサイズの削減は即座に現れます:Zone.jsは約33KB(gzip圧縮で10KB)のイーガーロードされたコードを占めています。
Zonelessモードで変更検知をトリガーするもの
すべての非同期操作をインターセプトするZone.jsがなければ、Angularは明示的な通知に依存します。フレームワークは次のいずれかの条件が発生すると変更検知をスケジュールします。
- テンプレートで読み取られたシグナルがその値を更新する
ChangeDetectorRef.markForCheck()が呼び出される(AsyncPipeによって自動的に)- コンポーネント入力が
ComponentRef.setInput()を介して変更される - テンプレートまたはホストリスナーコールバックが実行される(click、inputなど)
- 以前にダーティなビューがコンポーネントツリーにアタッチされる
import { Component, signal, computed } from '@angular/core';
@Component({
selector: 'app-counter',
template: `
<div class="counter">
<button (click)="decrement()">-</button>
<span>{{ count() }}</span>
<button (click)="increment()">+</button>
<p>Double: {{ doubled() }}</p>
</div>
`
})
export class CounterComponent {
// シグナル更新はテンプレートに自動的に通知する
count = signal(0);
doubled = computed(() => this.count() * 2);
increment() {
this.count.update(v => v + 1);
// markForCheck()は不要 - シグナルが通知を処理する
}
decrement() {
this.count.update(v => v - 1);
}
}シグナルはzonelessモードの自然な相棒です。シグナルの値が変更されると、Angularはそれに依存するテンプレートを正確に把握し、それらのビューのみに対してターゲットを絞った変更検知をスケジュールします。
ChangeDetectionStrategy.OnPushの採用はzoneless互換性への推奨ステップですが、厳密には必須ではありません。デフォルトの変更検知戦略はzonelessモードでも機能します。ただし、OnPushはコンポーネントが入力が変更されたときまたはmarkForCheck()が呼び出されたときのみ再レンダリングされることを保証し、zonelessのメンタルモデルと自然に一致します。
既存アプリケーションの移行:一般的な落とし穴
Zone.jsからzonelessへの移行は、既存のアプリケーションにとってめったに1行の変更で済みません。Zone.jsの暗黙的な動作に依存していたいくつかのパターンには、明示的な処理が必要です。
setTimeoutとsetIntervalは更新をトリガーしない
Zone.jsモードでは、setTimeoutコールバックが自動的に変更検知をトリガーします。zonelessモードでは、トリガーしません。
@Component({
selector: 'app-user-status',
template: `<span>{{ statusMessage }}</span>`
})
export class UserStatusComponent {
statusMessage = 'Loading...';
ngOnInit() {
setTimeout(() => {
// Zone.jsはここでCDをトリガーする - zonelessはトリガーしない
this.statusMessage = 'Ready';
}, 2000);
}
}import { Component, signal } from '@angular/core';
@Component({
selector: 'app-user-status',
template: `<span>{{ statusMessage() }}</span>`
})
export class UserStatusComponent {
statusMessage = signal('Loading...');
ngOnInit() {
setTimeout(() => {
// シグナル更新はAngularに自動的に通知する
this.statusMessage.set('Ready');
}, 2000);
}
}Reactive Formsには明示的な通知が必要
FormControl.setValue()またはpatchValue()を介したフォーム状態の変更は、zonelessモードでは自動的に変更検知をトリガーしません。2つのアプローチがこれを解決します:フォームのObservableをmarkForCheck()に接続するか、シグナルを介してフォームデータを公開します。
import { Component, inject, signal } from '@angular/core';
import { FormControl, ReactiveFormsModule } from '@angular/forms';
import { toSignal } from '@angular/core/rxjs-interop';
import { debounceTime, distinctUntilChanged } from 'rxjs';
@Component({
selector: 'app-search',
imports: [ReactiveFormsModule],
template: `
<input [formControl]="searchControl" placeholder="Search..." />
<p>Results for: {{ searchTerm() }}</p>
`
})
export class SearchComponent {
searchControl = new FormControl('');
// Observableをシグナルに変換して自動テンプレート更新
searchTerm = toSignal(
this.searchControl.valueChanges.pipe(
debounceTime(300),
distinctUntilChanged()
),
{ initialValue: '' }
);
}Angularの面接対策はできていますか?
インタラクティブなシミュレーター、flashcards、技術テストで練習しましょう。
Zone.jsなしのサーバーサイドレンダリング
zoneless AngularでのSSRには特別な注意が必要です。Zone.jsは以前、アプリケーションがシリアライズに適した「安定」状態に達したことをAngularが判断するのに役立ちました。それがなければ、PendingTasksサービスがその役割を果たします。
import { Component, inject, signal } from '@angular/core';
import { PendingTasks } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { firstValueFrom } from 'rxjs';
@Component({
selector: 'app-data-loader',
template: `
@if (data()) {
<div>{{ data()!.title }}</div>
} @else {
<div>Loading...</div>
}
`
})
export class DataLoaderComponent {
private http = inject(HttpClient);
private pendingTasks = inject(PendingTasks);
data = signal<{ title: string } | null>(null);
ngOnInit() {
// PendingTasks.run()は完了するまでSSRシリアライゼーションを防ぐ
this.pendingTasks.run(async () => {
const result = await firstValueFrom(
this.http.get<{ title: string }>('/api/data')
);
this.data.set(result);
});
}
}PendingTasksがなければ、Angularは非同期データがロードされる前にページをシリアライズし、空のコンテンツをクライアントに送信します。
パフォーマンスベンチマーク:Zone.js vs Zoneless
Zone.jsを削除することによるパフォーマンス向上は3つのカテゴリに分類されます。
| メトリック | Zone.js | Zoneless | 改善 | |--------|---------|----------|-------------| | 初期バンドルサイズ | +33KB生 / +10KB gzip | 0KBオーバーヘッド | 100%削減 | | 変更検知サイクル(典型的なアプリ) | インタラクションあたり150-300 | インタラクションあたり5-15 | 80-95%削減 | | スタックトレースの深さ | Zoneフレームが8-12追加 | クリーンなネイティブトレース | 即座の明確性 | | Time to Interactive(TTI) | ベースライン | 15-25%高速 | Zoneブートストラップを排除 |
最も劇的な改善は、大量の非同期アクティビティを持つアプリケーションで現れます:HTTPポーリング、WebSocket接続、タイマー、複雑なイベントハンドラー。これらはすべて、以前は不要な変更検知サイクルをトリガーしていましたが、zonelessモードでは完全に排除されます。
一部のサードパーティライブラリは内部的にZone.jsに依存しています。モーダルダイアログ、特定のWeb Componentラッパー、一部のアニメーションライブラリは、zonelessモードで予期しない動作をする可能性があります。本番環境にzonelessをデプロイする前に、常にライブラリの統合を徹底的にテストしてください。
移行後も存続するNgZone API
よくある誤解:Zone.jsを削除することは、すべてのNgZone参照を削除することを意味します。これは正しくありません。NgZone.run()とNgZone.runOutsideAngular()はzonelessアプリケーションと互換性があり、共有ライブラリに保持する必要があります。これらを削除すると、Zone.jsを使用してこれらのライブラリを消費するアプリケーションでパフォーマンスの低下を引き起こす可能性があります。
ただし、3つのNgZone Observableは削除する必要があります。
NgZone.onMicrotaskEmpty- zonelessモードでは発行されないNgZone.onUnstable- zonelessモードでは発行されないNgZone.onStable- zonelessモードでは発行されない
これらのObservableを使用したタイミング依存のロジックを@angular/coreのafterNextRender()またはafterEveryRender()に置き換えます。
Angular 19実験版からAngular 21デフォルトまで
zoneless APIの進化は明確な安定化パスに従います。
- Angular 18.1:
provideExperimentalZonelessChangeDetection()が実験的に導入 - Angular 19: 実験的APIが洗練され、より広範なエコシステムテスト
- Angular 20:
provideZonelessChangeDetection()に名前変更、安定版に昇格 - Angular 20.2: APIが完全に安定し、動作の変更は予想されない
- Angular 21: zonelessが
ng newプロジェクトのデフォルトになり、プロバイダー呼び出しは不要
Angular 19を使用しているチームの場合、アップグレードパスは簡単です:Angular 20に更新し、provideExperimentalZonelessChangeDetectionをprovideZonelessChangeDetectionに置き換え、zone.jsポリフィルを削除します。Angular 19は2026年5月19日にサポート終了となるため、この移行は時間的に重要です。
Angular面接の準備をしていますか?SharpSkillのAngular面接質問モジュールは、面接官がますます尋ねるzonelessシナリオを含む変更検知パターンを詳しくカバーしています。より広範な概要については、Angular面接質問トップ25ガイドをご覧ください。Angularシグナルモジュールも、zonelessを可能にする反応性モデルをカバーしています。
結論
- Zone.jsを削除すると、33KBのバンドル重量が排除され、130以上のブラウザAPIをインターセプトしていたモンキーパッチング層が削除されます
- Angularシグナルは、zonelessを実用的にする明示的な反応性モデルを提供し、状態が変更されたときにテンプレートに自動的に通知します
- 移行には、
setTimeout/setIntervalパターン、Reactive Forms、タイミング依存のロジックをシグナルベースまたはmarkForCheck()アプローチに変換する必要があります - SSRアプリケーションは、Zone.jsの安定性検出を置き換えるために
PendingTasksを採用する必要があります NgZone.run()とNgZone.runOutsideAngular()は、下位互換性のために共有ライブラリに保持する必要があります- Angular 19の実験的API(
provideExperimentalZonelessChangeDetection)は、Angular 20の安定版provideZonelessChangeDetection()に直接マッピングされるため、アップグレードは名前変更操作になります - サードパーティライブラリの互換性は主要なリスク要因であり、本番環境へのデプロイ前に徹底的なテストが必要です
今すぐ練習を始めましょう!
面接シミュレーターと技術テストで知識をテストしましょう。
タグ
共有
関連記事

Angular 18 Signals完全ガイド:新しいリアクティブAPIとZoneless変更検知
Angular 18のSignals、Zoneless変更検知、signal-based APIを解説。input()、model()、viewChild()の実践的な使い方をコード例付きで紹介します。

Angular スタンドアロンコンポーネント:移行ガイドとベストプラクティス 2026
NgModule ベースの Angular アプリケーションをスタンドアロンコンポーネントへ移行するための完全ガイド。公式 CLI の3ステップ移行手順、遅延読み込み、ルーティング、Angular 21 のベストプラクティスを網羅的に解説します。

Angular面接質問トップ25:成功への完全ガイド
2026年に最も多く聞かれるAngular面接質問25選。詳細な回答、コード例、Angular開発者ポジションを獲得するためのコツを紹介します。