← Back to Chapters

Introduction to NgRx

⚡Introduction to NgRx

Angular State Management NgRx Store

? Quick Overview

NgRx is a powerful state management library for Angular applications, inspired by Redux. It provides a predictable state container using Actions, Reducers, and Selectors, making medium-to-large Angular applications more scalable, testable, and maintainable.

Instead of each component managing its own state independently, NgRx centralizes it in a single Store, so the entire app state becomes easier to reason about and debug.

? Key Concepts in NgRx

  • Store: A single source of truth for the application state.
  • Actions: Plain objects that describe events in the app (for example: [Auth] User Logged In).
  • Reducers: Pure functions that handle state transitions based on actions.
  • Selectors: Functions to retrieve slices of state from the store.
  • Effects: Handle side effects such as API calls or other async tasks.

? Syntax & Theory

In NgRx, you typically define:

  • Actions using helpers like createAction.
  • Reducers using createReducer and on to respond to actions.
  • Selectors using createSelector or simple functions to read state.
  • Store registration using StoreModule.forRoot() or forFeature().

The typical data flow is: Component dispatches Action → Reducer updates Store → Selectors read updated state.

? Basic Counter Example with NgRx

A minimal example showing actions, reducer, and store registration.

? View Code Example
// actions/counter.actions.ts - defines the counter-related actions
import { createAction } from '@ngrx/store';

export const increment = createAction('[Counter] Increment');
export const decrement = createAction('[Counter] Decrement');
export const reset = createAction('[Counter] Reset');

// reducers/counter.reducer.ts - pure function describing how state changes
import { createReducer, on } from '@ngrx/store';
import { increment, decrement, reset } from './counter.actions';

export const initialState = 0;

export const counterReducer = createReducer(
  initialState,
  on(increment, state => state + 1),
  on(decrement, state => state - 1),
  on(reset, _ => 0)
);

// app.module.ts - register the reducer in the root store
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { StoreModule } from '@ngrx/store';
import { AppComponent } from './app.component';
import { counterReducer } from './reducers/counter.reducer';

@NgModule({
  declarations: [AppComponent],
  imports: [
    BrowserModule,
    StoreModule.forRoot({ count: counterReducer })
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

? How This Works (Explanation)

  • Actions like increment() describe what happened (for example, user clicked a button).
  • Reducer receives the current state and the dispatched action, and returns a new state (for example, state + 1).
  • The Store (configured with StoreModule.forRoot({ count: counterReducer })) holds the current value of count for the whole app.
  • Components can dispatch actions and select state using store.dispatch(increment()) and store.select('count').

Sample timeline:

  • Initial state: count = 0
  • Dispatch increment() → state becomes 1
  • Dispatch increment() again → state becomes 2
  • Dispatch reset() → state becomes 0

✅ Tips & Best Practices

  • Start small—don’t use NgRx for every tiny local state; prefer it for complex or global state.
  • Organize actions, reducers, and selectors into separate folders for clarity.
  • Use NgRx DevTools for powerful debugging and time-travel inspection of state changes.
  • Keep reducers pure: avoid API calls, random values, or direct DOM access inside them.
  • Use modern NgRx APIs like createReducer and createAction to reduce boilerplate.

? When Should You Use NgRx?

  • Your app has shared state used by many unrelated components (for example, auth user, cart, settings).
  • You need time-travel debugging or easy reproduction of bugs from specific state snapshots.
  • Your business logic around state changes is complex and must be easily testable.
  • Multiple teams work on the same Angular codebase, and you want a predictable, standardized state flow.

? Try It Yourself

  • Create a simple counter app using NgRx actions, reducer, and store (like the example above).
  • Use selectors to read the counter value in a component and display it in the template.
  • Add reset functionality and test dispatching actions from buttons in your component.
  • Install and experiment with NgRx DevTools to inspect actions and state over time.
  • Extend the example to manage another piece of state, like a list of todos or products.