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 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.
You now understand NgRx's core building blocks. Next, learn about Angular Signals, the reactive primitive powering much of modern Angular.