Skip to content

Angular NgRx

NgRx is a Redux-inspired state management library built specifically for Angular and RxJS. This lesson introduces its core building blocks and when they're actually worth adopting.

What Is NgRx?

NgRx provides a single, centralized store for application state, updated only through dispatched actions handled by pure reducer functions, with side effects (like API calls) isolated into a separate "effects" layer.

This structure makes state changes traceable, testable in isolation, and consistent across a large codebase, at the cost of more boilerplate than component or service state alone.

// actions
export const loadUsers = createAction('[User List] Load Users');
export const usersLoaded = createAction('[User API] Users Loaded', props<{ users: User[] }>());

// reducer
export const userReducer = createReducer(
  initialState,
  on(loadUsers, state => ({ ...state, loading: true })),
  on(usersLoaded, (state, { users }) => ({ ...state, users, loading: false }))
);

Reducers are pure functions: given the current state and an action, they return a brand-new state object, never mutating the existing one.

The Core NgRx Building Blocks

createAction('[Source] Event Name', props<{ payload: T }>())
createReducer(initialState, on(action, (state, payload) => newState))
createSelector(featureSelector, state => state.someSlice)
createEffect(() => actions$.pipe(ofType(action), switchMap(() => api.call())))
  • Actions describe *what happened*, named descriptively as [Source] Event.
  • Reducers are pure functions that compute new state from the current state and an action.
  • Selectors read (and can derive/combine) slices of state efficiently, with built-in memoization.
  • Effects handle side effects (like HTTP calls) triggered by actions, dispatching new actions with the result.

NgRx Cheatsheet

The vocabulary and building blocks of the NgRx data flow.

Concept Purpose
Action Describes an event that occurred
Reducer Pure function computing new state from an action
Store Holds the single, centralized application state tree
Selector Reads (and derives) a piece of state, memoized
Effect Handles side effects (API calls) triggered by actions
dispatch(action) Sends an action to the store to be handled by reducers/effects
store.select(selector) Subscribes to a specific slice of state

The NgRx Data Flow

A component dispatches an action describing intent ("load users"). A reducer immediately updates state synchronously (like setting a loading flag). An effect listens for that same action, performs the actual side effect (the HTTP call), and dispatches a follow-up action with the result, which a reducer then folds into state.

  • Component dispatches loadUsers().
  • Reducer sets loading: true synchronously.
  • Effect listens for loadUsers, calls the API.
  • Effect dispatches usersLoaded({ users }) when the API responds.
  • Reducer sets users and loading: false.

Reading State With Selectors

Components never read the store's raw state tree directly, they use selectors, which are memoized and automatically re-run only when the relevant slice of state actually changes.

export const selectUsers = createSelector(
  (state: AppState) => state.users,
  usersState => usersState.users
);

// in a component
private store = inject(Store);
users = this.store.selectSignal(selectUsers);

When NgRx Is Actually Worth It

NgRx adds meaningful structure and boilerplate, worthwhile once an app's state genuinely benefits from strict traceability, time-travel debugging, and consistent update patterns across a large team. For small to medium apps, component and service state (covered in earlier lessons) are often sufficient.

Situation Recommendation
Small app, few shared state pieces Component/service state (signals) is usually enough
Large team, complex cross-cutting state NgRx's structure and tooling pay off
Need for strict auditability/time-travel debugging NgRx (or a similar store) is a strong fit

Common Mistakes

  • Adopting NgRx for a small app before state complexity actually justifies the added boilerplate.
  • Mutating state directly inside a reducer instead of returning a new state object.
  • Putting side effects (API calls) directly inside a reducer instead of an effect; reducers must stay pure.
  • Reading the store's state tree directly instead of through memoized selectors.

Key Takeaways

  • NgRx centralizes state in a single store, updated only through dispatched actions and pure reducers.
  • Effects isolate side effects (like API calls) away from reducers, which must remain pure.
  • Selectors provide memoized, efficient access to specific slices of state.
  • NgRx is a deliberate trade-off: more structure and traceability in exchange for more boilerplate.

Pro Tip

If you're evaluating whether to adopt NgRx, try modeling the same feature with a plain signal-based service first; if that genuinely becomes hard to reason about as complexity grows, that's a much stronger signal to introduce NgRx than adopting it upfront out of habit.