Aller au contenu

Lab 18 : NgRx — Gestion d'état Redux

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.

Fenêtre de terminal
npm install @ngrx/store @ngrx/effects @ngrx/store-devtools

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

Section intitulée « É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 }))
);

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

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

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 {}

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>

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.