NgRx Store (Classic)
NgRx implements the Redux pattern for Angular. State is immutable; changes happen through actions dispatched to reducers.
Installation
Section titled “Installation”npm install @ngrx/store @ngrx/effects @ngrx/store-devtoolsCore Concepts
Section titled “Core Concepts”| 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 |
Actions
Section titled “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 }>());Reducer
Section titled “Reducer”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 })));Selectors
Section titled “Selectors”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);Effects
Section titled “Effects”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) {}}Wiring (app.config.ts)
Section titled “Wiring (app.config.ts)”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 }), ],};Using in a Component
Section titled “Using in a Component”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" />}NgRx vs Signal Store
Section titled “NgRx vs Signal Store”| 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 |

