Appearance
DestroyRef et takeUntilDestroyed : Gérer la destruction des composants dans Angular
Angular 16+
DestroyRef
est une nouvelle fonctionnalité introduite dans Angular 16+.
Comprendre DestroyRef par un exemple concret
Imaginez que vous ayez une application de messagerie instantanée. Quand un utilisateur quitte une conversation, vous devez vous assurer que toutes les souscriptions aux messages sont correctement arrêtées pour éviter les fuites mémoire. C'est exactement ce que DestroyRef
nous aide à faire !
Implémentation Technique
Commençons par voir comment utiliser DestroyRef
dans notre application de gestion d'utilisateurs.
ts
import { Component, DestroyRef, inject } from '@angular/core';
import { UserService } from '../../core/services/user.service';
import { User } from './user.interface';
@Component({
selector: 'app-user',
standalone: true,
template: `
@if (user) {
<div>{{ user.name }}</div>
}
`
})
export class UserComponent {
private userService = inject(UserService);
private destroyRef = inject(DestroyRef);
user: User | null = null;
constructor() {
this.userService.getUser(1).subscribe(user => {
this.user = user;
});
this.destroyRef.onDestroy(() => {
// Logique de nettoyage
console.log('Composant détruit, nettoyage effectué');
});
}
}
ASTUCE
DestroyRef est particulièrement utile pour créer des fonctions réutilisables de nettoyage que vous pouvez utiliser dans plusieurs composants.
Création d'une Fonction Réutilisable
Voici comment créer une fonction utilitaire pour gérer les souscriptions :
ts
export function destroyScope() {
const subscriptions = new Subscription();
inject(DestroyRef).onDestroy(() => {
subscriptions.unsubscribe();
});
return subscriptions;
}
Utilisation dans un composant :
ts
import { Component } from '@angular/core';
import { destroyScope } from './utils';
@Component({
selector: 'app-user-list',
standalone: true,
template: `
@for (user of users; track user.id;) {
<div>{{ user.name }}</div>
}
`
})
export class UserListComponent {
private subscriptions = destroyScope();
users: User[] = [];
constructor(private userService: UserService) {
this.subscriptions.add(
this.userService.getUsers().subscribe(users => {
this.users = users;
})
);
}
}
ATTENTION
La fonction destroyScope() ne peut être utilisée que dans un contexte d'injection (constructor ou initialisation de propriété). Elle ne fonctionnera pas dans ngOnInit() ou d'autres méthodes du cycle de vie.
Différence avec ngOnDestroy
DestroyRef offre plusieurs avantages par rapport à ngOnDestroy :
- Injection de dépendances plutôt qu'implémentation d'interface
- Possibilité de créer des fonctions utilitaires réutilisables
- Plus flexible dans son utilisation
ts
// Approche traditionnelle avec ngOnDestroy
export class TraditionalComponent implements OnDestroy {
ngOnDestroy() {
// Logique de nettoyage
}
}
// Approche moderne avec DestroyRef
export class ModernComponent {
constructor(destroyRef: DestroyRef) {
destroyRef.onDestroy(() => {
// Logique de nettoyage
});
}
}
Utilisation de takeUntilDestroyed
takeUntilDestroyed
est un opérateur RxJS spécialement conçu par Angular qui permet de gérer automatiquement la destruction des observables. C'est une alternative plus élégante à notre fonction destroyScope
.
ASTUCE
takeUntilDestroyed
est disponible depuis Angular 16 dans @angular/core/rxjs-interop
.
Voici comment l'utiliser :
ts
import { Component, inject } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { UserService } from '../../core/services/user.service';
import { User } from './user.interface';
@Component({
selector: 'app-user',
standalone: true,
template: `
@if (user) {
<div>{{ user.name }}</div>
}
`
})
export class UserComponent {
private userService = inject(UserService);
user: User | null = null;
constructor() {
// L'observable sera automatiquement détruit quand le composant sera détruit
this.userService.getUser(1).pipe(
takeUntilDestroyed()
).subscribe(user => {
this.user = user;
});
}
}
Utilisation hors du constructeur
Si vous devez utiliser takeUntilDestroyed
en dehors du constructeur, vous devez injecter explicitement DestroyRef
:
ts
import { Component, DestroyRef, inject } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { UserService } from '../../core/services/user.service';
import { User } from './user.interface';
@Component({
selector: 'app-user-list',
standalone: true,
template: `
@for (user of users; track user.id;) {
<div>{{ user.name }}</div>
}
`
})
export class UserListComponent {
private destroyRef = inject(DestroyRef);
users: User[] = [];
ngOnInit() {
this.userService.getUsers().pipe(
takeUntilDestroyed(this.destroyRef)
).subscribe(users => {
this.users = users;
});
}
}
ATTENTION
takeUntilDestroyed()
sans paramètre ne peut être utilisé que dans un contexte d'injection (comme le constructeur). Pour l'utiliser ailleurs, vous devez passer explicitement DestroyRef
.
BONNE PRATIQUE
- Syntaxe plus concise que
DestroyRef.onDestroy()
- Intégration native avec RxJS
- Pas besoin de gérer manuellement les souscriptions
- Meilleure lisibilité du code
Préférez takeUntilDestroyed
à DestroyRef.onDestroy()
quand vous travaillez avec des observables, car c'est plus idiomatique et plus facile à maintenir.