Aller au contenu principal

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

ConceptRôle
StoreSource unique de vérité ; contient tout l'état de l'application
ActionDécrit ce qui s'est passé ([Source] Événement)
ReducerFonction pure : (état, action) → nouvel état
SelectorProjection mémoïsée de l'état
EffectGè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 ClassiqueSignal Store
PatronReduxNatif Signals
BoilerplateÉlevéFaible
DevToolsRedux DevToolsNgRx DevTools (Angular 17+)
RéactivitéObservablesSignals
Idéal pourGrandes équipes, état complexeApplications Angular modernes