Change Detection โ Default vs OnPush
Angular's change detection mechanism decides when to update the template. Default checks everything. OnPush checks only when needed. Signals make OnPush effortless.
Change detection is Angular's mechanism to check if the template needs updating. It compares current component values with previous values and re-renders if anything changed.
"Change detection = teacher checking homework โ kuch badla hai toh mark karo."
What triggers change detection?
- Browser events (click, keyup, submit)
- Timers (setTimeout, setInterval)
- HTTP responses
- Promises resolving
- Any async operation (Zone.js catches them all)
Angular runs change detection after every async operation. The question is HOW MANY components get checked each time.
// Every click triggers change detection
@Component({...})
export class AppComponent {
onClick() {
// After this runs, Angular checks ALL components
console.log('Click handled');
}
}ChangeDetectionStrategy.Default is the default behavior. When any async operation happens ANYWHERE in the app, Angular checks EVERY component in the component tree.
import { Component, ChangeDetectionStrategy } from '@angular/core';
@Component({
selector: 'app-slow',
template: `{{ data }}
`,
changeDetection: ChangeDetectionStrategy.Default // Default (or omit)
})
export class DefaultComponent { ... }
"Default = har baar sabke ghar check karo โ safe but slow."
How it works:
- User clicks a button in Component Z
- Angular runs change detection on ALL 100 components
- Even components unrelated to the click get checked
- Every check: compare previous vs current values
- 100 components ร 10 bindings = 1000 comparisons per click
When Default is fine: Small apps (<20 components), simple pages, prototypes. For larger apps, Default becomes a performance bottleneck.
ChangeDetectionStrategy.OnPush tells Angular to check a component ONLY when specific conditions are met โ not automatically on every async operation.
import { Component, ChangeDetectionStrategy } from '@angular/core';
@Component({
selector: 'app-fast',
template: `{{ data() }}
`,
changeDetection: ChangeDetectionStrategy.OnPush // Smart!
})
export class FastComponent { ... }
"OnPush = smart check โ sirf jab zaroor ho tab check karo."
Performance difference:
- Default: 100 components checked on EVERY async operation
- OnPush: component checked ONLY if its input/signal/event changed
- Typical app: click affects 3-5 components, not all 100
- OnPush makes large apps 10-20x faster
With signals, OnPush is even better: Signals tell Angular EXACTLY which components to check. No tree traversal, no comparison โ just direct notification to the affected components.
OnPush is strict about what triggers a check. Here's what DOES and DOESN'T work:
| Trigger | OnPush check? |
|---|---|
| @Input receives NEW object reference | โ Yes |
| @Input object MUTATED (same reference) | โ No |
| Component's own event (click, input) | โ Yes |
| Async pipe receives new value | โ Yes |
| Signal value changes | โ Yes (Angular 17+) |
| setTimeout / setInterval | โ No (unless markForCheck) |
| Service BehaviorSubject emits | โ No (unless async pipe) |
| Manual: cdr.markForCheck() | โ Yes |
"OnPush = strict bouncer โ sirf valid reasons pe andar jaane do."
// This DOES trigger OnPush:
@Input() items: Item[]; // Parent passes new array reference
// This DOES NOT trigger OnPush:
this.items.push(newItem); // Same array reference
// Solution: always create new reference:
@Input() items = signal- ([]);
this.items.update(list => [...list, newItem]); // New reference!
Best practice for Angular 17+:
import { Component, ChangeDetectionStrategy } from '@angular/core';
@Component({
selector: 'app-modern',
template: `{{ count() }}
`,
changeDetection: ChangeDetectionStrategy.OnPush // Default for all new code
})
export class ModernComponent {
count = signal(0); // With signals, OnPush is effortless
}
"Default = training wheels, OnPush = real cycling โ start with OnPush."
Rule: OnPush for EVERY component by default
- New Angular apps: set OnPush globally or per-component
- With signals, OnPush adds ZERO extra work โ signals auto-trigger checks
- Without signals, OnPush requires you to be careful about immutability
- Default strategy is only needed for legacy code that mutates data
Migration path:
- Add
signal()for component state - Set
changeDetection: OnPush - Replace
@Inputmutations with immutable patterns - Replace
subscribe()withtoSignal()or async pipe
Key Takeaways
- โ Change detection checks if template needs update after async operations
- โ Default strategy: checks EVERY component on every async event
- โ OnPush strategy: checks ONLY when inputs/events/signals change
- โ OnPush triggers: new @Input ref, component event, signal, async pipe
- โ OnPush does NOT trigger: mutation, setTimeout, service emission alone
- โ Use OnPush by default โ signals make it effortless and powerful
Want to track your progress?
Log in to save your place and pick up where you left off.
Progress track karna chahte ho?
Login karo apni progress save karne ke liye aur jahan chhoda tha wahan se shuru karo.
Login