Aller au contenu principal

Lab 18 : NgRx — Gestion d'état Redux

📖 Ressources

🚀 Code de départ

album-wholesale-v16

Dans ce lab, vous ajouterez une gestion d'état centralisée à l'application album wholesale en utilisant NgRx classic (@ngrx/store + @ngrx/effects). Vous gérerez la liste des albums et l'état de chargement via un store Redux, remplacerez les appels de service directs dans les composants par des dispatches et sélecteurs, et debuggerez avec Redux DevTools.

📝 Instructions

Étape 1 : Installer NgRx

npm install @ngrx/store @ngrx/effects @ngrx/store-devtools

Étape 2 : Définir l'état, les actions et le reducer

Créez src/app/store/album/album.actions.ts :

import { createAction, props } from '@ngrx/store';
import { Album } from '../../model/album.model';

export const loadAlbums = createAction('[Album] Load Albums');
export const loadAlbumsSuccess = createAction(
'[Album] Load Albums Success',
props<{ albums: Album[] }>()
);
export const loadAlbumsFailure = createAction(
'[Album] Load Albums Failure',
props<{ error: string }>()
);

Créez src/app/store/album/album.reducer.ts :

import { createReducer, on } from '@ngrx/store';
import { Album } from '../../model/album.model';
import { loadAlbums, loadAlbumsSuccess, loadAlbumsFailure } from './album.actions';

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 }))
);

Étape 3 : Créer un sélecteur

Créez src/app/store/album/album.selectors.ts :

import { createFeatureSelector, createSelector } from '@ngrx/store';
import { AlbumState } from './album.reducer';

export const selectAlbumState =
createFeatureSelector<AlbumState>('albums');

export const selectAllAlbums = createSelector(
selectAlbumState,
state => state.albums
);

export const selectAlbumsLoading = createSelector(
selectAlbumState,
state => state.loading
);

Étape 4 : Créer un effet

Créez src/app/store/album/album.effects.ts :

import { Injectable } from '@angular/core';
import { Actions, createEffect, ofType } from '@ngrx/effects';
import { switchMap, map, catchError, of } from 'rxjs';
import { AlbumService } from '../../services/album.service';
import { loadAlbums, loadAlbumsSuccess, loadAlbumsFailure } from './album.actions';

@Injectable()
export class AlbumEffects {
loadAlbums$ = createEffect(() =>
this.actions$.pipe(
ofType(loadAlbums),
switchMap(() =>
this.albumService.findAll().pipe(
map(albums => loadAlbumsSuccess({ albums })),
catchError(error =>
of(loadAlbumsFailure({ error: error.message }))
)
)
)
)
);

constructor(
private actions$: Actions,
private albumService: AlbumService
) {}
}

Étape 5 : Câbler le store dans AppModule

Ouvrez src/app/app.module.ts et ajoutez les modules NgRx dans le tableau imports :

import { StoreModule } from '@ngrx/store';
import { EffectsModule } from '@ngrx/effects';
import { StoreDevtoolsModule } from '@ngrx/store-devtools';
import { albumReducer } from './store/album/album.reducer';
import { AlbumEffects } from './store/album/album.effects';

@NgModule({
declarations: [ /* ... */ ],
imports: [
BrowserModule,
HttpClientModule,
// ... autres imports existants
StoreModule.forRoot({ albums: albumReducer }),
EffectsModule.forRoot([AlbumEffects]),
StoreDevtoolsModule.instrument({ maxAge: 25 }),
],
bootstrap: [AppComponent]
})
export class AppModule {}

Étape 6 : Utiliser le store dans un composant

Remplacez l'appel direct au service dans AlbumListComponent :

import { Store } from '@ngrx/store';
import { loadAlbums } from '../../store/album/album.actions';
import { selectAllAlbums, selectAlbumsLoading } from '../../store/album/album.selectors';

export class AlbumListComponent implements OnInit {
private store = inject(Store);

albums$ = this.store.select(selectAllAlbums);
loading$ = this.store.select(selectAlbumsLoading);

ngOnInit() {
this.store.dispatch(loadAlbums());
}
}
<mat-progress-bar *ngIf="loading$ | async" mode="indeterminate"></mat-progress-bar>

<app-album-card
*ngFor="let album of albums$ | async"
[album]="album">
</app-album-card>

Étape 7 : Vérifier avec Redux DevTools

Installez l'extension Chrome Redux DevTools et inspectez :

  • Chaque action dispatchée
  • L'état avant et après chaque action
  • Le débogage par voyage dans le temps (time-travel)

Comparez votre résultat avec le dossier de référence album-wholesale-v16-ngrx.