Skip to content

NgRx Store (Classic)

NgRx implements the Redux pattern for Angular. State is immutable; changes happen through actions dispatched to reducers.

Terminal window
npm install @ngrx/store @ngrx/effects @ngrx/store-devtools
Concept Role
Store Single source of truth; holds all application state
Action Describes what happened ([Source] Event)
Reducer Pure function: (state, action) → newState
Selector Memoized projection of state
Effect Handles side effects (HTTP, routing) triggered by actions
import { createAction, props } from '@ngrx/store';
export const loadAlbums = createAction('[Album] Load');
export const loadAlbumsSuccess = createAction(
'[Album] Load Success',
props<{ albums: Album[] }>()
);
export const loadAlbumsFailure = createAction(
'[Album] Load Failure',
props<{ error: string }>()
);
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 }))
);
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);
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) {}
}
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 }),
],
};
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>Loading…</p> }
@for (album of albums$ | async; track album.id) {
<app-album-card [album]="album" />
}
Feature NgRx Classic Signal Store
Pattern Redux Signals-native
Boilerplate High Low
DevTools Redux DevTools NgRx DevTools (Angular 17+)
Reactivity Observables Signals
Best for Large teams, complex state Modern Angular apps