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.