Angular 23 の Zoneless 変更検出は、Angular アーキテクチャにおける大きな革新だ。Zone.js への依存を排除し、変更検出をより予測可能で効率的にした。
Zone.js の歴史的負債
Zone.js は Angular 2 から変更検出のコアだった:
javascript
// Zone.js の動作原理
Zone.current.fork({
onInvokeTask: (delegate, zone, target, task, ...args) => {
const result = delegate.invokeTask(target, task, ...args);
ngZone.runOutsideAngular(() => {
ngZone.run(() => {});
});
return result;
}
});
Zone.js の問題:
- パフォーマンスオーバーヘッド:非同期操作ごとに変更検出が発生する可能性
- デバッグ困難:エラースタックが Zone.js でラップされる
- 予測不可能:いつ変更検出が発生するかわからない
- バンドルサイズ膨張:Zone.js 自体が約 15KB
Zoneless の動作原理
Zoneless モードでは、変更検出は開発者によって明示的に制御される:
typescript
@Component({
selector: 'app-counter',
template: `
<div>{{ count }}</div>
<button (click)="increment()">+1</button>
`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class CounterComponent {
count = 0;
increment() {
this.count++;
this.cdr.markForCheck();
}
constructor(private cdr: ChangeDetectorRef) {}
}
または Signal を使用:
typescript
@Component({
selector: 'app-counter',
template: `
<div>{{ count() }}</div>
<button (click)="increment()">+1</button>
`
})
export class CounterComponent {
count = signal(0);
increment() {
this.count.update(n => n + 1);
// Signal は自動的に変更検出をトリガー
}
}
Zone.js からの移行
移行は段階的に行う:
ステップ 1:Zoneless モードを有効化
typescript
// main.ts
bootstrapApplication(AppComponent, {
providers: [
provideZonelessChangeDetection()
]
});
ステップ 2:変更検出を修正
typescript
// 之前:Zone.js に依存
ngOnInit() {
this.userService.getUsers().subscribe(users => {
this.users = users;
});
}
// 之后:明示的にマーク
ngOnInit() {
this.userService.getUsers().subscribe(users => {
this.users = users;
this.cdr.markForCheck();
});
}
ステップ 3:Signal を使用(推奨)
typescript
@Component({...})
export class UserListComponent {
users = signal<User[]>([]);
ngOnInit() {
this.userService.getUsers().subscribe(users => {
this.users.set(users);
});
}
}
Zoneless の利点
利点 1:パフォーマンス向上
ベンチマーク結果:
- 初期レンダリング:20-30% 向上
- インタラクション応答:15-25% 向上
- メモリ使用量:10-20% 削減
利点 2:デバッグがより明確に
// Zone.js モード
Error at ZoneDelegate.invokeTask (zone.js:402)
at Zone.runTask (zone.js:175)
at Component.handleClick (component.ts:15) // 実際のエラー位置
// Zoneless モード
Error at Component.handleClick (component.ts:15) // 直接定位
利点 3:より予測可能に
Zoneless モードでは、変更検出は以下の場合のみトリガーされる:
- イベントハンドラ実行後
markForCheck()呼び出し後- Signal 値変化後
detectChanges()明示的呼び出し後
Signal と Zoneless の連携
Signal は Zoneless モードの最良のパートナー:
typescript
@Component({
selector: 'app-product-list',
template: `
<div *ngFor="let product of filteredProducts()">
{{ product.name }} - {{ product.price }}
</div>
`
})
export class ProductListComponent {
products = signal<Product[]>([]);
searchTerm = signal('');
filteredProducts = computed(() => {
const term = this.searchTerm();
return this.products().filter(p =>
p.name.toLowerCase().includes(term.toLowerCase())
);
});
constructor() {
effect(() => {
console.log('商品数:', this.filteredProducts().length);
});
}
}
まとめ
Angular 23 の Zoneless 変更検出は、アプリケーションをより効率的で予測可能にする。移行は段階的に行い、手動マークより Signal を優先的に使用すべきだ。2026 年の Angular 開発において、Zoneless はオプションではなく推奨されるベストプラクティスである。
