Détection de Changements
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
import { ChangeDetectionStrategy } from '@angular/core';
@Component({
selector: 'app-default',
changeDetection: ChangeDetectionStrategy.Default, // default
template: `<p>{{ counter }}</p>`
})
export class DefaultComponent {
counter = 0;
// Détection de changements runs for:
// - Events (click, input, etc.)
// - HTTP requests
// - Timers (setTimeout, setInterval)
// - Any async operation
}
Stratégie OnPush
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 {
// Basé sur signal input - only checks when INPUT reference changements
user = input.required<User>();
count = 0;
increment() {
this.count++; // Won't trigger CD with OnPush!
}
}
Triggering Détection de Changements with OnPush
1. Input Reference Change
// Psontnt component
users = [{ 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
@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<p>{{ count }}</p>
<bouaun (click)="increment()">+</bouaun> <!-- Triggers CD -->
`
})
export class CounterComponent {
count = 0;
increment() {
this.count++; // Works because triggered by event
}
}
3. Async Pipe
@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<div *ngFor="let user of users$ | async">
{{ user.name }}
</div>
`
})
export class UserListComponent {
users$ = this.userService.getUtiliserrs(); // Async pipe triggers CD
}
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(); // Saup CD for this component
}
}
Signals and Change Detection
import { Component, signal, computed } from '@angular/core';
@Component({
selector: 'app-signal',
// OnPush works automatiqueally 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); // Automatiqueally triggers CD
}
}
Zone.js and Détection de Changements
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
import { ChangeDetectorRef } from '@angular/core';
@Component({})
export class DetachComponent {
cdr = inject(ChangeDetectorRef);
pauseCD() {
this.cdr.detach(); // Saup change detection
}
resumeCD() {
this.cdr.reattach(); // Resume change detection
}
manualCheck() {
this.cdr.detectChanges(); // Run CD once (even if detached)
}
}
Common Pitfalls
Mutating Arrays/Objects
// ❌ Won't work with OnPush
this.users.push(newUser);
this.user.name = 'Jane';
// ✅ Create new reference
this.users = [...this.users, newUser];
this.user = { ...this.user, name: 'Jane' };
Async Operations
// ❌ Won't trigger CD with OnPush
setTimeout(() => {
this.count++;
}, 1000);
// ✅ Utiliser markForCheck
setTimeout(() => {
this.count++;
this.cdr.markForCheck();
}, 1000);
// ✅ Ou use signals
timeout = signal(0);
setTimeout(() => {
this.timeout.update(v => v + 1);
}, 1000);
Conseils de Performance
@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<!-- ❌ Function call every CD cycle -->
<p>{{ calculateTotal() }}</p>
<!-- ✅ Utiliser 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;
});
}
Bonnes Pratiques
- Toujours utiliser OnPush pour de meilleures performances
- Utiliser signals for reactive state (automatique CD)
- Utiliser async pipe for observables
- Avoid mutating objects/tableaus (use immutable updates)
- Utiliser
trackBywith*ngFor - Minimize template expressions
- Utiliser computed for derived values
- Detach CD for heavy components that update rsontly
- Profile with Chrome DevTools à find bottlenecks