Appearance
Cycle de vie dans NgRx Signal Store
Le cycle de vie dans NgRx Signal Store correspond aux différentes phases qu'un store traverse depuis sa création jusqu'à sa destruction. Comprendre et utiliser ces cycles de vie vous permet d'initialiser correctement votre état, de réagir aux changements, et de nettoyer les ressources lorsqu'elles ne sont plus nécessaires.
Les cycles de vie dans la vie quotidienne
Pour illustrer ce concept, pensez à l'ouverture et à la fermeture d'un magasin chaque jour. À l'ouverture (initialisation), les employés préparent le magasin, vérifient l'inventaire et mettent en place les présentoirs. Pendant la journée (phase active), ils servent les clients et réapprovisionnent les rayons. À la fermeture (destruction), ils ferment la caisse, nettoient le magasin et sécurisent les locaux. Chaque phase a ses propres responsabilités et processus spécifiques.
withHooks : Ajouter des hooks de cycle de vie
NgRx Signal Store propose l'utilisation de withHooks
pour définir des fonctions qui seront exécutées à des moments spécifiques du cycle de vie d'un store :
ts
import { signalStore, withState, withHooks } from '@ngrx/signals';
import { inject } from '@angular/core';
import { UserService } from './user.service';
interface UserState {
users: User[];
selectedUserId: number | null;
isLoading: boolean;
}
export const UserStore = signalStore(
{ providedIn: 'root' },
withState<UserState>({
users: [],
selectedUserId: null,
isLoading: false
}),
withHooks({
// Hook exécuté lors de l'initialisation du store
onInit(store) {
console.log('UserStore initialized');
// Exemple : charger les utilisateurs au démarrage
const userService = inject(UserService);
store.loadUsers();
},
// Hook exécuté lorsque le store est détruit
onDestroy() {
console.log('UserStore destroyed');
// Nettoyage des ressources si nécessaire
}
})
);
DISPONIBILITÉ
Notez que si votre store est injecté au niveau racine avec providedIn: 'root'
, le hook onDestroy
ne sera jamais appelé car le store existe pendant toute la durée de vie de l'application.
Hooks disponibles
onInit
Le hook onInit
est exécuté lorsque le store est initialisé, c'est-à-dire lorsqu'il est injecté pour la première fois. C'est l'endroit idéal pour :
- Charger des données initiales
- Configurer des écouteurs d'événements
- Initialiser des services externes
ts
withHooks({
onInit(store) {
// Injecter des services
const http = inject(HttpClient);
const router = inject(Router);
// Charger des données initiales
http.get<User[]>('/api/users').subscribe(users => {
patchState(store, { users, isLoading: false });
});
// Configurer des écouteurs d'événements
const subscription = someObservable.subscribe(/* ... */);
// Retourner une fonction de nettoyage (facultatif)
return () => subscription.unsubscribe();
}
})
onDestroy
Le hook onDestroy
est appelé lorsque le store est détruit. Il est utile pour :
- Annuler des abonnements
- Sauvegarder l'état
- Libérer des ressources
ts
withHooks({
onDestroy(store) {
// Sauvegarder l'état dans localStorage
localStorage.setItem('userState', JSON.stringify({
selectedUserId: store.selectedUserId()
}));
// Nettoyer les ressources
console.log('UserStore cleaned up');
}
})
Fonction de nettoyage depuis onInit
Le hook onInit
peut également retourner une fonction de nettoyage qui sera appelée lorsque le store est détruit, similaire à un effect cleanup dans React :
ts
withHooks({
onInit(store) {
// Configuration
const subscription = someObservable.subscribe(/* ... */);
// Retourner une fonction de nettoyage
return () => {
console.log('Cleaning up from onInit');
subscription.unsubscribe();
};
}
})
Hooks personnalisés
Vous pouvez également créer des hooks personnalisés en fonction de vos besoins :
ts
import { signalStore, withState, withHooks, type SignalStoreHooks } from '@ngrx/signals';
// Définir des hooks personnalisés
type MyCustomHooks = SignalStoreHooks & {
afterDataLoaded?: (store: MyStore) => void;
};
// Utiliser les hooks personnalisés
export const MyStore = signalStore(
withState({ data: null, isLoaded: false }),
withHooks<typeof store, MyCustomHooks>({
onInit(store) {
loadData().then(data => {
patchState(store, { data, isLoaded: true });
// Appeler le hook personnalisé si défini
if (this.afterDataLoaded) {
this.afterDataLoaded(store);
}
});
},
// Hook personnalisé
afterDataLoaded(store) {
console.log('Data has been loaded', store.data());
}
})
);
Propagation des erreurs
Les erreurs levées dans les hooks du cycle de vie se propagent normalement, vous devez donc les gérer correctement :
ts
withHooks({
onInit(store) {
try {
// Code qui pourrait lever une erreur
const data = JSON.parse(localStorage.getItem('complexData') || '{}');
patchState(store, { data });
} catch (error) {
console.error('Failed to initialize store', error);
// Gérer l'erreur gracieusement
patchState(store, { error: 'Erreur d\'initialisation' });
}
}
})
Partage de données entre hooks
Si vous avez besoin de partager des données entre différents hooks, vous pouvez utiliser des variables dans la portée de withHooks
:
ts
withHooks((() => {
// Variables privées partagées entre les hooks
let subscription: Subscription | null = null;
let lastUpdated: Date | null = null;
return {
onInit(store) {
lastUpdated = new Date();
subscription = someObservable.subscribe(data => {
patchState(store, { data });
lastUpdated = new Date();
});
},
onDestroy() {
if (subscription) {
subscription.unsubscribe();
}
console.log('Last update was at:', lastUpdated);
}
};
})())