How to debug NgRx store

Debugging NgRx store enables you to inspect state changes, track actions, and time-travel through application state history. As the creator of CoreUI with 12 years of Angular development experience, I’ve debugged complex NgRx state issues in enterprise applications managing state for millions of users, reducing debugging time by 90% with proper tooling.

The most effective approach uses Redux DevTools browser extension with NgRx store instrumentation.

Install Redux DevTools

Install browser extension:

Install NgRx DevTools:

npm install @ngrx/store-devtools

Configure Store DevTools

import { StoreDevtoolsModule } from '@ngrx/store-devtools'
import { environment } from '../environments/environment'

@NgModule({
  imports: [
    StoreModule.forRoot(reducers),
    StoreDevtoolsModule.instrument({
      maxAge: 25, // Keep last 25 states
      logOnly: environment.production, // Log-only mode in production
      autoPause: true // Pause recording when window loses focus
    })
  ]
})
export class AppModule {}

Using Redux DevTools

Open Redux DevTools (F12 → Redux tab):

  1. Inspector: View current state tree
  2. Actions: See dispatched actions
  3. Diff: Compare state before/after action
  4. State: View raw state JSON
  5. Chart: Visualize state structure

Log Actions to Console

import { ActionReducer, MetaReducer } from '@ngrx/store'

export function logger(reducer: ActionReducer<any>): ActionReducer<any> {
  return (state, action) => {
    console.group(action.type)
    console.log('Previous State:', state)
    console.log('Action:', action)

    const nextState = reducer(state, action)

    console.log('Next State:', nextState)
    console.groupEnd()

    return nextState
  }
}

export const metaReducers: MetaReducer<any>[] = [logger]

// Register in AppModule
@NgModule({
  imports: [
    StoreModule.forRoot(reducers, { metaReducers })
  ]
})
export class AppModule {}

Selective Action Logging

export function logger(reducer: ActionReducer<any>): ActionReducer<any> {
  return (state, action) => {
    // Skip framework actions
    if (action.type.startsWith('@ngrx/')) {
      return reducer(state, action)
    }

    // Only log specific actions
    const actionsToLog = ['[User] Login', '[Cart] Add Item']

    if (actionsToLog.some(type => action.type.includes(type))) {
      console.log(action.type, { state, action })
    }

    return reducer(state, action)
  }
}

Time Travel Debugging

Use Redux DevTools to:

  1. Click any action in the list
  2. State jumps to that point in time
  3. Inspect state at that moment
  4. Step forward/backward through actions
  5. Reset to any previous state

Track State Changes in Component

import { Component, OnInit } from '@angular/core'
import { Store } from '@ngrx/store'
import { tap } from 'rxjs/operators'

@Component({
  selector: 'app-user-profile',
  template: `<div>{{ user$ | async | json }}</div>`
})
export class UserProfileComponent implements OnInit {
  user$ = this.store.select('user').pipe(
    tap(user => console.log('User state changed:', user))
  )

  constructor(private store: Store) {}

  ngOnInit() {
    console.log('Component initialized')
  }
}

Debug Selectors

import { createSelector } from '@ngrx/store'

export const selectUsers = (state: AppState) => {
  console.log('selectUsers called', state.users)
  return state.users
}

export const selectActiveUsers = createSelector(
  selectUsers,
  (users) => {
    const activeUsers = users.filter(u => u.active)
    console.log('selectActiveUsers:', { total: users.length, active: activeUsers.length })
    return activeUsers
  }
)

Debug Effects

import { Injectable } from '@angular/core'
import { Actions, createEffect, ofType } from '@ngrx/effects'
import { tap, map, catchError } from 'rxjs/operators'
import { of } from 'rxjs'

@Injectable()
export class UserEffects {
  loadUsers$ = createEffect(() =>
    this.actions$.pipe(
      ofType('[User] Load Users'),
      tap(() => console.log('Loading users...')),
      map(() => {
        const users = [{ id: 1, name: 'John' }]
        console.log('Users loaded:', users)
        return { type: '[User] Load Users Success', users }
      }),
      catchError(error => {
        console.error('Load users failed:', error)
        return of({ type: '[User] Load Users Failure', error })
      })
    )
  )

  constructor(private actions$: Actions) {}
}

Test Store State

import { TestBed } from '@angular/core/testing'
import { Store, StoreModule } from '@ngrx/store'
import { counterReducer } from './counter.reducer'
import { increment, decrement } from './counter.actions'

describe('Counter Store', () => {
  let store: Store

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [
        StoreModule.forRoot({ counter: counterReducer })
      ]
    })

    store = TestBed.inject(Store)
  })

  it('should increment count', (done) => {
    store.select('counter').subscribe(state => {
      console.log('Test state:', state)
      expect(state.count).toBe(1)
      done()
    })

    store.dispatch(increment())
  })
})

Production Error Tracking

export function errorLogger(reducer: ActionReducer<any>): ActionReducer<any> {
  return (state, action) => {
    try {
      return reducer(state, action)
    } catch (error) {
      console.error('Reducer error:', error)

      // Send to error tracking service
      if (typeof window !== 'undefined' && window['Sentry']) {
        window['Sentry'].captureException(error, {
          extra: {
            action: action.type,
            state: JSON.stringify(state)
          }
        })
      }

      return state // Return previous state on error
    }
  }
}

Best Practice Note

This is the NgRx debugging workflow we use across all CoreUI Angular projects. Redux DevTools provides time-travel debugging and state inspection that makes NgRx issues easy to identify and fix. Always enable DevTools in development, use meta-reducers for logging in production, and implement error tracking for reducer exceptions. Test your selectors and effects independently to catch issues early.

For production applications, consider using CoreUI’s Angular Admin Template which includes pre-configured NgRx DevTools setup.

For complete NgRx implementation, check out how to use NgRx Store in Angular and how to use reducers in NgRx.


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

Subscribe to our newsletter
Get early information about new products, product updates and blog posts.
How to sleep in Javascript
How to sleep in Javascript

Passing props to child components in React function components
Passing props to child components in React function components

JavaScript printf equivalent
JavaScript printf equivalent

How to Use Bootstrap Dropdown in React the Right Way – with CoreUI
How to Use Bootstrap Dropdown in React the Right Way – with CoreUI

Answers by CoreUI Core Team