Aller au contenu principal

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 trackBy with *ngFor
  • Minimize template expressions
  • Utiliser computed for derived values
  • Detach CD for heavy components that update rsontly
  • Profile with Chrome DevTools à find bottlenecks