How to use NgRx in Angular

NgRx provides Redux-inspired state management for Angular applications, enabling predictable state updates and centralized data flow. As the creator of CoreUI with 12 years of Angular development experience, I’ve architected NgRx stores for enterprise applications managing complex state across hundreds of components.

The most maintainable approach follows the standard NgRx pattern with actions, reducers, effects, and selectors organized by feature.

Install NgRx

Install NgRx packages:

ng add @ngrx/store @ngrx/effects @ngrx/store-devtools

Create State Interface

Create src/app/store/user/user.state.ts:

export interface User {
  id: string
  name: string
  email: string
}

export interface UserState {
  users: User[]
  loading: boolean
  error: string | null
  selectedUser: User | null
}

export const initialState: UserState = {
  users: [],
  loading: false,
  error: null,
  selectedUser: null
}

Create Actions

Create src/app/store/user/user.actions.ts:

import { createAction, props } from '@ngrx/store'
import { User } from './user.state'

export const loadUsers = createAction('[User] Load Users')

export const loadUsersSuccess = createAction(
  '[User] Load Users Success',
  props<{ users: User[] }>()
)

export const loadUsersFailure = createAction(
  '[User] Load Users Failure',
  props<{ error: string }>()
)

export const selectUser = createAction(
  '[User] Select User',
  props<{ userId: string }>()
)

export const addUser = createAction(
  '[User] Add User',
  props<{ user: User }>()
)

export const deleteUser = createAction(
  '[User] Delete User',
  props<{ userId: string }>()
)

Create Reducer

Create src/app/store/user/user.reducer.ts:

import { createReducer, on } from '@ngrx/store'
import { initialState } from './user.state'
import * as UserActions from './user.actions'

export const userReducer = createReducer(
  initialState,

  on(UserActions.loadUsers, (state) => ({
    ...state,
    loading: true,
    error: null
  })),

  on(UserActions.loadUsersSuccess, (state, { users }) => ({
    ...state,
    users,
    loading: false
  })),

  on(UserActions.loadUsersFailure, (state, { error }) => ({
    ...state,
    loading: false,
    error
  })),

  on(UserActions.selectUser, (state, { userId }) => ({
    ...state,
    selectedUser: state.users.find(u => u.id === userId) || null
  })),

  on(UserActions.addUser, (state, { user }) => ({
    ...state,
    users: [...state.users, user]
  })),

  on(UserActions.deleteUser, (state, { userId }) => ({
    ...state,
    users: state.users.filter(u => u.id !== userId)
  }))
)

Create Effects

Create src/app/store/user/user.effects.ts:

import { Injectable } from '@angular/core'
import { Actions, createEffect, ofType } from '@ngrx/effects'
import { of } from 'rxjs'
import { map, catchError, switchMap } from 'rxjs/operators'
import { UserService } from '../../services/user.service'
import * as UserActions from './user.actions'

@Injectable()
export class UserEffects {
  loadUsers$ = createEffect(() =>
    this.actions$.pipe(
      ofType(UserActions.loadUsers),
      switchMap(() =>
        this.userService.getUsers().pipe(
          map(users => UserActions.loadUsersSuccess({ users })),
          catchError(error =>
            of(UserActions.loadUsersFailure({ error: error.message }))
          )
        )
      )
    )
  )

  constructor(
    private actions$: Actions,
    private userService: UserService
  ) {}
}

Create Selectors

Create src/app/store/user/user.selectors.ts:

import { createFeatureSelector, createSelector } from '@ngrx/store'
import { UserState } from './user.state'

export const selectUserState = createFeatureSelector<UserState>('user')

export const selectAllUsers = createSelector(
  selectUserState,
  (state: UserState) => state.users
)

export const selectUsersLoading = createSelector(
  selectUserState,
  (state: UserState) => state.loading
)

export const selectUsersError = createSelector(
  selectUserState,
  (state: UserState) => state.error
)

export const selectSelectedUser = createSelector(
  selectUserState,
  (state: UserState) => state.selectedUser
)

export const selectUserById = (userId: string) =>
  createSelector(selectAllUsers, (users) =>
    users.find(u => u.id === userId)
  )

Register Store

Update src/app/app.config.ts:

import { ApplicationConfig } from '@angular/core'
import { provideStore } from '@ngrx/store'
import { provideEffects } from '@ngrx/effects'
import { provideStoreDevtools } from '@ngrx/store-devtools'
import { userReducer } from './store/user/user.reducer'
import { UserEffects } from './store/user/user.effects'

export const appConfig: ApplicationConfig = {
  providers: [
    provideStore({ user: userReducer }),
    provideEffects([UserEffects]),
    provideStoreDevtools({ maxAge: 25 })
  ]
}

Use in Component

import { Component, OnInit } from '@angular/core'
import { Store } from '@ngrx/store'
import { Observable } from 'rxjs'
import * as UserActions from './store/user/user.actions'
import * as UserSelectors from './store/user/user.selectors'
import { User } from './store/user/user.state'

@Component({
  selector: 'app-user-list',
  template: `
    <div *ngIf="loading$ | async">Loading...</div>
    <div *ngIf="error$ | async as error" class="error">{{ error }}</div>

    <ul>
      <li *ngFor="let user of users$ | async" (click)="selectUser(user.id)">
        {{ user.name }}
      </li>
    </ul>
  `
})
export class UserListComponent implements OnInit {
  users$: Observable<User[]>
  loading$: Observable<boolean>
  error$: Observable<string | null>

  constructor(private store: Store) {
    this.users$ = this.store.select(UserSelectors.selectAllUsers)
    this.loading$ = this.store.select(UserSelectors.selectUsersLoading)
    this.error$ = this.store.select(UserSelectors.selectUsersError)
  }

  ngOnInit() {
    this.store.dispatch(UserActions.loadUsers())
  }

  selectUser(userId: string) {
    this.store.dispatch(UserActions.selectUser({ userId }))
  }
}

Best Practice Note

This is the same NgRx architecture we use in CoreUI’s Angular admin templates for enterprise state management. The separation of actions, reducers, effects, and selectors provides maintainable, testable code with predictable state updates. Use selectors for derived state and effects for side effects like API calls.

For production applications, consider using CoreUI’s Angular Admin Template which includes pre-configured NgRx store with authentication, routing, and feature modules.

If you’re implementing authentication, you might also want to learn how to logout a user in Angular with NgRx state management.


Speed up your responsive apps and websites with fully-featured, ready-to-use open-source admin panel templates—free to use and built for efficiency.


About the Author