Change Detection in Angular
What is Change Detection?
Change detection is how Angular decides when to update the DOM. When data changes, Angular checks the component tree and re-renders any component whose data has changed. Angular 21 defaults to zoneless change detection powered by Signals — the most efficient approach yet.
Default vs OnPush Strategy
| Strategy | When it checks | Best for |
|---|---|---|
Default | Every event, timer, HTTP response (entire tree) | Simple apps, prototyping |
OnPush | Only when inputs change by reference, events fire, or markForCheck() is called | Performance-critical components |
| Zoneless (Signals) | Only when a signal that the template reads changes | Angular 21 default — best performance |
OnPush Change Detection
import { Component, Input, ChangeDetectionStrategy } from '@angular/core';
interface Product {
id: number;
name: string;
price: number;
}
@Component({
selector: 'app-product',
standalone: true,
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<div>
<h3>{{ product.name }}</h3>
<p>${{ product.price }}</p>
</div>
`
})
export class ProductComponent {
@Input() product!: Product;
// Angular only re-checks this component when:
// 1. The 'product' input reference changes (new object)
// 2. An event fires inside this component
// 3. markForCheck() is called manually
}
// IMPORTANT: With OnPush, mutating the object won't trigger re-render
// BAD: this.product.price = 20; // same reference — no update
// GOOD: this.product = { ...this.product, price: 20 }; // new reference
Zoneless Change Detection with Signals (Angular 21)
In Angular 21, Signals are the recommended way to manage state. Angular tracks exactly which signals a template reads and only updates that component when those signals change — no Zone.js, no full-tree checks.
import { Component, signal, computed } from '@angular/core';
@Component({
selector: 'app-counter',
standalone: true,
template: `
<p>Count: {{ count() }}</p>
<p>Doubled: {{ doubled() }}</p>
<button (click)="increment()">+1</button>
`
})
export class CounterComponent {
count = signal(0);
doubled = computed(() => this.count() * 2);
increment() {
this.count.update(v => v + 1);
}
// Angular only re-renders this component when count() changes
// No Zone.js needed — pure signal-based reactivity
}
import { ApplicationConfig, provideExperimentalZonelessChangeDetection } from '@angular/core';
export const appConfig: ApplicationConfig = {
providers: [
// Enable zoneless change detection (default in Angular 21)
provideExperimentalZonelessChangeDetection(),
]
};
Manual Control with ChangeDetectorRef
import { Component, ChangeDetectionStrategy, ChangeDetectorRef, inject } from '@angular/core';
@Component({
selector: 'app-manual',
standalone: true,
changeDetection: ChangeDetectionStrategy.OnPush,
template: `<p>{{ data }}</p>`
})
export class ManualComponent {
private cdr = inject(ChangeDetectorRef);
data = 'initial';
updateFromExternalSource() {
// Called from a third-party library callback (outside Angular)
this.data = 'updated';
this.cdr.markForCheck(); // tell Angular to check this component
}
pauseDetection() {
this.cdr.detach(); // stop checking this component
}
resumeDetection() {
this.cdr.reattach(); // resume checking
this.cdr.detectChanges(); // run one check immediately
}
}