Change Detection Strategy
Esta página aún no está disponible en tu idioma.
Source: Angular Best Practices
Change Detection Overview
Section titled “Change Detection Overview”Change detection is Angular’s core mechanism for detecting when application data changes and updating the view accordingly.
The Problem with Default Strategy
Section titled “The Problem with Default Strategy”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.
OnPush: Performance Optimization
Section titled “OnPush: Performance Optimization”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.”
Implementation
Section titled “Implementation”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;}@Component({ selector: 'app-button', template: '<button (click)="handleClick()">Click me</button>', changeDetection: ChangeDetectionStrategy.OnPush})export class ButtonComponent { @Output() onClick = new EventEmitter<void>();
handleClick() { this.onClick.emit(); }}@Component({ selector: 'app-user', template: '<div>{{ user$ | async | json }}</div>', changeDetection: ChangeDetectionStrategy.OnPush})export class UserComponent { @Input() user$: Observable<User>;}Default vs OnPush Performance
Section titled “Default vs OnPush Performance”Default Strategy (❌ Inefficient)
Section titled “Default Strategy (❌ Inefficient)”// 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 checksOnPush Strategy (✅ Efficient)
Section titled “OnPush Strategy (✅ Efficient)”// Same scenario with OnPush
// Angular checks:// - App component// - List component// - Only item components whose @Input data changed ← Fewer checks!// Total: Only necessary checksWhen to Use Each Strategy
Section titled “When to Use Each Strategy”Use Default Strategy When:
Section titled “Use Default Strategy When:”- Building simple applications with few components
- Not worried about performance yet
- Learning Angular (easier to understand)
Use OnPush Strategy When:
Section titled “Use OnPush Strategy When:”- Building performance-critical applications
- Have lists with many items
- Working with observables for state management
- Receiving feedback that app feels slow
Pattern: Immutable Data with OnPush
Section titled “Pattern: Immutable Data with OnPush”The key to OnPush is immutable data. Never mutate objects; create new ones instead.
❌ Bad: Mutating Data
Section titled “❌ Bad: Mutating Data”// Item component with OnPushexport class ItemComponent { @Input() item: Item;
updateName(name: string) { this.item.name = name; // ❌ Mutating! Change detection won't trigger }}
// Parent componentitems.push(item); // ❌ Mutating array! Won't trigger change detection in child✅ Good: Immutable Data
Section titled “✅ Good: Immutable Data”// Item component with OnPushexport class ItemComponent { @Input() item: Item;
updateName(name: string) { const updated = { ...this.item, name }; // ✅ Create new object this.itemUpdated.emit(updated); }}
// Parent componentthis.items = [...this.items, newItem]; // ✅ Create new arrayPractical Examples
Section titled “Practical Examples”Example 1: Item List Component
Section titled “Example 1: Item List Component”@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-item', template: ` <div> <span>{{ item.name }}</span> <button (click)="delete()">Delete</button> </div> `, changeDetection: ChangeDetectionStrategy.OnPush})export class ItemComponent { @Input() item: Item; @Output() delete = new EventEmitter<string>();
deleteItem() { this.delete.emit(this.item.id); }}@Component({ selector: 'app-items', template: ` <app-item-list [items]="items()" (itemDeleted)="onItemDeleted($event)"> </app-item-list> `, changeDetection: ChangeDetectionStrategy.OnPush})export class ItemsComponent { private itemService = inject(ItemService); items = signal<Item[]>([]);
ngOnInit() { this.itemService.getItems().subscribe(items => { this.items.set(items); // ✅ Update signal }); }
onItemDeleted(itemId: string) { this.items.update(items => items.filter(i => i.id !== itemId) ); }}Example 2: Dashboard with OnPush
Section titled “Example 2: Dashboard with OnPush”@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); }); }}Migration Strategy
Section titled “Migration Strategy”If your component isn’t using OnPush yet:
- Add OnPush to the component
- Verify all inputs are immutable
- Test the component thoroughly
- Check browser DevTools for any visual differences
- Gradual migration — Start with leaf components first
OnPush Checklist
Section titled “OnPush Checklist”- 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
asyncpipe - No unexpected DOM updates missing
- Component tested with different input values
Common Pitfalls
Section titled “Common Pitfalls”Pitfall 1: Forgetting Immutability
Section titled “Pitfall 1: Forgetting Immutability”// ❌ Won't work with OnPushthis.item.name = 'New Name';
// ✅ Correctthis.item = { ...this.item, name: 'New Name' };Pitfall 2: Not Triggering Parent Updates
Section titled “Pitfall 2: Not Triggering Parent Updates”// ❌ Child update doesn't trigger parent with OnPushdeleteItem(id: string) { this.items.splice(this.items.indexOf(id), 1); // Mutating!}
// ✅ Emit event for parent to handledeleteItem(id: string) { this.delete.emit(id);}Pitfall 3: Forgetting Async Pipe
Section titled “Pitfall 3: Forgetting Async Pipe”// ❌ Observable won't trigger change detectiondata: Observable<Item> = this.service.getItem();template: '{{ data | json }}' // Still using observable
// ✅ Use async pipetemplate: '{{ (data | async) | json }}'Performance Impact
Section titled “Performance Impact”Enabling OnPush can improve performance by:
- 30-50% in large component trees
- Faster change detection cycles
- Reduced CPU usage
- Better mobile performance