Ir al contenido

Change Detection Strategy

Esta página aún no está disponible en tu idioma.

Source: Angular Best Practices

Change detection is Angular’s core mechanism for detecting when application data changes and updating the view accordingly.

By default, Angular checks all components in the entire tree every time ANY change happens:

  • User click
  • API response
  • Timer fires
  • Any async operation

This creates significant resource overhead, especially in large applications.

From Indrajit Saha’s Angular Best Practices:

“Change detection serves as a fundamental mechanism in Angular, profoundly influencing application performance. It involves detecting alterations in your application’s data and subsequently updating the view. By default, Angular employs the ‘Default’ change detection strategy, which can impose significant resource overhead. However, optimizing performance is feasible through the adoption of the ‘OnPush’ change detection strategy.”

import { Component, Input, ChangeDetectionStrategy } from '@angular/core';
@Component({
selector: 'app-item',
template: '<div>{{ item.name }}</div>',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ItemComponent {
@Input() item: Item;
}
// Scenario: List with 100 item components
// User clicks a button to load data
// Angular checks:
// - App component
// - List component
// - ALL 100 item components ← Even if their @Input data didn't change!
// - Other components on the page
// Total: Hundreds of checks
// Same scenario with OnPush
// Angular checks:
// - App component
// - List component
// - Only item components whose @Input data changed ← Fewer checks!
// Total: Only necessary checks
  • Building simple applications with few components
  • Not worried about performance yet
  • Learning Angular (easier to understand)
  • Building performance-critical applications
  • Have lists with many items
  • Working with observables for state management
  • Receiving feedback that app feels slow

The key to OnPush is immutable data. Never mutate objects; create new ones instead.

// Item component with OnPush
export class ItemComponent {
@Input() item: Item;
updateName(name: string) {
this.item.name = name; // ❌ Mutating! Change detection won't trigger
}
}
// Parent component
items.push(item); // ❌ Mutating array! Won't trigger change detection in child
// Item component with OnPush
export class ItemComponent {
@Input() item: Item;
updateName(name: string) {
const updated = { ...this.item, name }; // ✅ Create new object
this.itemUpdated.emit(updated);
}
}
// Parent component
this.items = [...this.items, newItem]; // ✅ Create new array
@Component({
selector: 'app-item-list',
template: `
<app-item *ngFor="let item of items"
[item]="item"
(delete)="onDelete($event)">
</app-item>
`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ItemListComponent {
@Input() items: Item[] = [];
@Output() itemDeleted = new EventEmitter<string>();
onDelete(itemId: string) {
this.itemDeleted.emit(itemId);
}
}
@Component({
selector: 'app-dashboard',
template: `
<div>
<app-metric [data]="metrics()"></app-metric>
<app-chart [data]="chartData()"></app-chart>
<app-table [data]="tableData()"></app-table>
</div>
`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class DashboardComponent {
private dashboardService = inject(DashboardService);
metrics = signal<Metric[]>([]);
chartData = signal<ChartData | null>(null);
tableData = signal<TableRow[]>([]);
ngOnInit() {
this.dashboardService.getMetrics().subscribe(data => {
this.metrics.set(data);
});
}
}

If your component isn’t using OnPush yet:

  1. Add OnPush to the component
  2. Verify all inputs are immutable
  3. Test the component thoroughly
  4. Check browser DevTools for any visual differences
  5. Gradual migration — Start with leaf components first
  • Component marked with ChangeDetectionStrategy.OnPush
  • All @Input() data is immutable
  • Never mutate objects or arrays in component
  • Events trigger appropriate parent updates
  • Observables in template use async pipe
  • No unexpected DOM updates missing
  • Component tested with different input values
// ❌ Won't work with OnPush
this.item.name = 'New Name';
// ✅ Correct
this.item = { ...this.item, name: 'New Name' };
// ❌ Child update doesn't trigger parent with OnPush
deleteItem(id: string) {
this.items.splice(this.items.indexOf(id), 1); // Mutating!
}
// ✅ Emit event for parent to handle
deleteItem(id: string) {
this.delete.emit(id);
}
// ❌ Observable won't trigger change detection
data: Observable<Item> = this.service.getItem();
template: '{{ data | json }}' // Still using observable
// ✅ Use async pipe
template: '{{ (data | async) | json }}'

Enabling OnPush can improve performance by:

  • 30-50% in large component trees
  • Faster change detection cycles
  • Reduced CPU usage
  • Better mobile performance