NgRx Store (Classique)
NgRx implémente le patron Redux pour Angular. L'état est immuable ; les modifications se font via des actions dispatchées vers des reducers.
Installation
npm install @ngrx/store @ngrx/effects @ngrx/store-devtools
Concepts fondamentaux
| Concept | Rôle |
|---|---|
| Store | Source unique de vérité ; contient tout l'état de l'application |
| Action | Décrit ce qui s'est passé ([Source] Événement) |
| Reducer | Fonction pure : (état, action) → nouvel état |
| Selector | Projection mémoïsée de l'état |
| Effect | Gère les effets secondaires (HTTP, routage) déclenchés par des actions |
Actions
import { createAction, props } from '@ngrx/store';
export const loadAlbums = createAction('[Album] Charger');
export const loadAlbumsSuccess = createAction(
'[Album] Chargement réussi',
props<{ albums: Album[] }>()
);
export const loadAlbumsFailure = createAction(
'[Album] Échec du chargement',
props<{ error: string }>()
);
Reducer
import { createReducer, on } from '@ngrx/store';
export interface AlbumState {
albums: Album[];
loading: boolean;
error: string | null;
}
const initialState: AlbumState = { albums: [], loading: false, error: null };
export const albumReducer = createReducer(
initialState,
on(loadAlbums, state => ({ ...state, loading: true })),
on(loadAlbumsSuccess, (state, { albums }) => ({ ...state, albums, loading: false })),
on(loadAlbumsFailure, (state, { error }) => ({ ...state, error, loading: false }))
);
Sélecteurs
import { createFeatureSelector, createSelector } from '@ngrx/store';
const selectAlbumState = createFeatureSelector<AlbumState>('albums');
export const selectAllAlbums = createSelector(selectAlbumState, s => s.albums);
export const selectLoading = createSelector(selectAlbumState, s => s.loading);
Effets
import { Injectable } from '@angular/core';
import { Actions, createEffect, ofType } from '@ngrx/effects';
import { switchMap, map, catchError, of } from 'rxjs';
@Injectable()
export class AlbumEffects {
load$ = createEffect(() =>
this.actions$.pipe(
ofType(loadAlbums),
switchMap(() =>
this.albumService.getAlbums().pipe(
map(albums => loadAlbumsSuccess({ albums })),
catchError(err => of(loadAlbumsFailure({ error: err.message })))
)
)
)
);
constructor(private actions$: Actions, private albumService: AlbumService) {}
}
Câblage (app.config.ts)
import { provideStore } from '@ngrx/store';
import { provideEffects } from '@ngrx/effects';
import { provideStoreDevtools } from '@ngrx/store-devtools';
export const appConfig: ApplicationConfig = {
providers: [
provideStore({ albums: albumReducer }),
provideEffects([AlbumEffects]),
provideStoreDevtools({ maxAge: 25 }),
],
};
Utilisation dans un composant
export class AlbumListComponent {
private store = inject(Store);
albums$ = this.store.select(selectAllAlbums);
loading$ = this.store.select(selectLoading);
ngOnInit() {
this.store.dispatch(loadAlbums());
}
}
@if (loading$ | async) { <p>Chargement…</p> }
@for (album of albums$ | async; track album.id) {
<app-album-card [album]="album" />
}
NgRx Classique vs Signal Store
| Fonctionnalité | NgRx Classique | Signal Store |
|---|---|---|
| Patron | Redux | Natif Signals |
| Boilerplate | Élevé | Faible |
| DevTools | Redux DevTools | NgRx DevTools (Angular 17+) |
| Réactivité | Observables | Signals |
| Idéal pour | Grandes équipes, état complexe | Applications Angular modernes |