Change Detection
How it Works
Section titled “How it Works”Angular checks if component data has changed and updates the DOM accordingly.
@Component({ selector: 'app-user', template: ` <p>{{ name }}</p> <button (click)="changeName()">Change</button> `})export class UserComponent { name = 'John';
changeName() { this.name = 'Jane'; // Triggers change detection }}Default Strategy
Section titled “Default Strategy”import { ChangeDetectionStrategy } from '@angular/core';
@Component({ selector: 'app-default', changeDetection: ChangeDetectionStrategy.Default, // default template: `<p>{{ counter }}</p>`})export class DefaultComponent { counter = 0;
// Change detection runs for: // - Events (click, input, etc.) // - HTTP requests // - Timers (setTimeout, setInterval) // - Any async operation}OnPush Strategy
Section titled “OnPush Strategy”import { Component, ChangeDetectionStrategy, input } from '@angular/core';
@Component({ selector: 'app-optimized', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: ` <p>{{ user().name }}</p> <p>{{ count }}</p> `})export class OptimizedComponent { // Signal-based input - only checks when INPUT reference changes user = input.required<User>(); count = 0;
increment() { this.count++; // Won't trigger CD with OnPush! }}Triggering Change Detection with OnPush
Section titled “Triggering Change Detection with OnPush”1. Input Reference Change
Section titled “1. Input Reference Change”// Parent componentusers = [{ name: 'John' }];
updateUser() { // ❌ Won't trigger CD this.users[0].name = 'Jane';
// ✅ Will trigger CD this.users = [{ name: 'Jane' }];
// ✅ Also works this.users = [...this.users]; this.users[0] = { name: 'Jane' };}2. Events in Template
Section titled “2. Events in Template”@Component({ changeDetection: ChangeDetectionStrategy.OnPush, template: ` <p>{{ count }}</p> <button (click)="increment()">+</button> <!-- Triggers CD --> `})export class CounterComponent { count = 0;
increment() { this.count++; // Works because triggered by event }}3. Async Pipe
Section titled “3. Async Pipe”@Component({ changeDetection: ChangeDetectionStrategy.OnPush, template: ` <div *ngFor="let user of users$ | async"> {{ user.name }} </div> `})export class UserListComponent { users$ = this.userService.getUsers(); // Async pipe triggers CD}4. Manual with ChangeDetectorRef
Section titled “4. Manual with ChangeDetectorRef”import { ChangeDetectorRef } from '@angular/core';
@Component({ changeDetection: ChangeDetectionStrategy.OnPush})export class ManualComponent { cdr = inject(ChangeDetectorRef); count = 0;
updateFromCallback() { setTimeout(() => { this.count++; this.cdr.markForCheck(); // Mark for CD }, 1000); }
updateImmediate() { this.count++; this.cdr.detectChanges(); // Run CD immediately }
ngOnDestroy() { this.cdr.detach(); // Stop CD for this component }}Signals and Change Detection
Section titled “Signals and Change Detection”import { Component, signal, computed } from '@angular/core';
@Component({ selector: 'app-signal', // OnPush works automatically with signals! changeDetection: ChangeDetectionStrategy.OnPush, template: ` <p>Count: {{ count() }}</p> <p>Double: {{ double() }}</p> <button (click)="increment()">+</button> `})export class SignalComponent { count = signal(0); double = computed(() => this.count() * 2);
increment() { this.count.update(v => v + 1); // Automatically triggers CD }}Zone.js and Change Detection
Section titled “Zone.js and Change Detection”import { NgZone } from '@angular/core';
@Component({})export class ZoneComponent { ngZone = inject(NgZone);
// Run outside Angular zone (no CD) runOutsideAngular() { this.ngZone.runOutsideAngular(() => { setInterval(() => { console.log('No CD triggered'); }, 1000); }); }
// Run inside Angular zone (triggers CD) runInsideAngular() { this.ngZone.run(() => { this.count++; }); }}Detach/Reattach
Section titled “Detach/Reattach”import { ChangeDetectorRef } from '@angular/core';
@Component({})export class DetachComponent { cdr = inject(ChangeDetectorRef);
pauseCD() { this.cdr.detach(); // Stop change detection }
resumeCD() { this.cdr.reattach(); // Resume change detection }
manualCheck() { this.cdr.detectChanges(); // Run CD once (even if detached) }}Common Pitfalls
Section titled “Common Pitfalls”Mutating Arrays/Objects
Section titled “Mutating Arrays/Objects”// ❌ Won't work with OnPushthis.users.push(newUser);this.user.name = 'Jane';
// ✅ Create new referencethis.users = [...this.users, newUser];this.user = { ...this.user, name: 'Jane' };Async Operations
Section titled “Async Operations”// ❌ Won't trigger CD with OnPushsetTimeout(() => { this.count++;}, 1000);
// ✅ Use markForChecksetTimeout(() => { this.count++; this.cdr.markForCheck();}, 1000);
// ✅ Or use signalstimeout = signal(0);setTimeout(() => { this.timeout.update(v => v + 1);}, 1000);Performance Tips
Section titled “Performance Tips”@Component({ changeDetection: ChangeDetectionStrategy.OnPush, template: ` <!-- ❌ Function call every CD cycle --> <p>{{ calculateTotal() }}</p>
<!-- ✅ Use computed/pipe --> <p>{{ total }}</p>
<!-- ❌ Complex expression --> <p>{{ users.filter(u => u.active).length }}</p>
<!-- ✅ Pre-calculate --> <p>{{ activeUserCount }}</p> `})export class PerformanceComponent { users = signal([...]);
// Pre-calculate activeUserCount = computed(() => { return this.users().filter(u => u.active).length; });}Best Practices
Section titled “Best Practices”- Always use OnPush for better performance
- Use signals for reactive state (automatic CD)
- Use async pipe for observables
- Avoid mutating objects/arrays (use immutable updates)
- Use
trackBywith*ngFor - Minimize template expressions
- Use computed for derived values
- Detach CD for heavy components that update rarely
- Profile with Chrome DevTools to find bottlenecks

