Chapter 15: Change Detection Strategies
Change detection is the mechanism that keeps Angular applications up to date. Whenever a user clicks a button, an HTTP response arrives, or a timer fires, Angular must decide:
Which parts of the DOM should be updated?
This chapter explains how Angular’s change detection works, the available strategies (Default and OnPush), how Zones tie into the process, and how Signals introduce a new era of precision updates.
15.1 The Basics of Change Detection
Angular’s change detection system ensures that values in templates always match the component state.
Example:
@Component({
selector: 'app-counter',
standalone: true,
template: `<p>Count: {{ count }}</p>`
})
export class CounterComponent {
count = 0;
increment() { this.count++; }
}
- When
increment()is called, Angular re-checks bindings. - If
countchanged, Angular updates the DOM.
15.2 Zone.js Recap
Angular uses Zone.js to know when “something happened.”
- Zone.js patches async APIs like
setTimeout,Promise,addEventListener. - After each async event, Angular runs change detection across the whole component tree.
✅ Simple and automatic.
❌ Can be inefficient — even unrelated components get checked.
15.3 Default Strategy
By default, every time change detection runs, Angular:
- Walks the entire component tree.
- Re-evaluates every binding.
This ensures correctness, but in large apps it may become costly.
15.4 OnPush Strategy
To optimize, Angular provides ChangeDetectionStrategy.OnPush.
import { ChangeDetectionStrategy, Component } from '@angular/core';
@Component({
selector: 'app-user',
standalone: true,
template: `<p>{{ user.name }}</p>`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class UserComponent {
@Input() user!: { name: string };
}
With OnPush, Angular only checks the component when:
- An @Input() reference changes.
- An event inside the component fires.
- Manually triggered with
ChangeDetectorRef.markForCheck().
✅ Fewer checks, better performance.
❌ Requires immutable data patterns (so Angular can detect changes via reference).
15.5 Manual Control with ChangeDetectorRef
Angular exposes APIs for fine-grained control:
constructor(private cd: ChangeDetectorRef) {}
ngOnInit() {
setTimeout(() => {
this.cd.detectChanges(); // run change detection manually
}, 1000);
}
Useful in edge cases (e.g., third-party libs, WebSockets).
15.6 Signals and Change Detection
Signals change the game.
- Instead of checking the whole tree, Angular tracks exact dependencies.
- When a signal changes, Angular knows which bindings depend on it and updates only those.
Example:
@Component({
selector: 'app-cart',
standalone: true,
template: `
<p>Total items: {{ count() }}</p>
<button (click)="add()">Add</button>
`
})
export class CartComponent {
items = signal<string[]>([]);
count = computed(() => this.items().length);
add() {
this.items.update(list => [...list, 'Book']);
}
}
Here, only {{ count() }} re-renders when items changes — Angular skips everything else.
✅ No need for OnPush.
✅ No need for manual immutability.
✅ Ultra-efficient reactivity.
15.7 Comparing Strategies
| Strategy | Mechanism | Best Use Case |
|---|---|---|
| Default | Full tree check after any async event | Small/medium apps where simplicity wins |
| OnPush | Check only on input changes or events | Performance-sensitive large apps |
| Signals | Fine-grained dependency tracking | Modern Angular apps (v16+) |
15.8 Migration Path
- For existing apps: keep using
OnPushfor optimization. - For new apps: adopt signals where possible — it simplifies reactivity.
- Both models coexist: you can mix
OnPushwith signals today.
15.9 Summary
- Change detection is how Angular syncs component state with the DOM.
- Zone.js runs global checks, which can be wasteful.
OnPushreduces checks but requires immutability discipline.- Signals provide a future-forward way to achieve fine-grained updates automatically.