Lab 18: NgRx — Redux State Management
📖 Resources
Section titled “📖 Resources”🚀 Starter Code
Section titled “🚀 Starter Code”album-wholesale-v16
In this lab you will add centralized state management to the album wholesale app using NgRx classic (@ngrx/store + @ngrx/effects). You will manage the album list and loading state through a Redux store, replace direct service calls in components with store dispatches and selectors, and debug with Redux DevTools.
📝 Instructions
Section titled “📝 Instructions”Step 1: Install NgRx
Section titled “Step 1: Install NgRx”npm install @ngrx/store @ngrx/effects @ngrx/store-devtoolsStep 2: Define State, Actions and Reducer
Section titled “Step 2: Define State, Actions and Reducer”Create 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 }>());Create 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 })));Step 3: Create a Selector
Section titled “Step 3: Create a Selector”Create 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);Step 4: Create an Effect
Section titled “Step 4: Create an Effect”Create 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 ) {}}Step 5: Wire the Store in AppModule
Section titled “Step 5: Wire the Store in AppModule”Open src/app/app.module.ts and add the NgRx modules to the imports array:
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, // ... other existing imports StoreModule.forRoot({ albums: albumReducer }), EffectsModule.forRoot([AlbumEffects]), StoreDevtoolsModule.instrument({ maxAge: 25 }), ], bootstrap: [AppComponent]})export class AppModule {}Step 6: Use the Store in a Component
Section titled “Step 6: Use the Store in a Component”Replace the direct service call in 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>Step 7: Verify with Redux DevTools
Section titled “Step 7: Verify with Redux DevTools”Install the Redux DevTools Chrome Extension and inspect:
- Each dispatched action
- State before and after each action
- Time-travel debugging
Compare your result with the album-wholesale-v16-ngrx reference folder.

