State Management
NgRx
Master NgRx, covering global store design, actions, reducers, selectors, and handling asynchronous side-effects with NgRx Effects.
1. Learning Objectives
In this lesson, you will master NgRx, the Redux-inspired global state management framework for Angular. By the end of this topic, you will be able to:
- Explain the principles of Redux and unidirectional data flow in single-page applications.
- Define and trigger state transitions using NgRx Actions and Reducers.
- Write performant, memoized queries to extract store slices using Selectors.
- Manage asynchronous side-effects (like HTTP calls) cleanly using NgRx Effects.
- Inspect and debug active state timelines using Redux DevTools integration.
2. Overview
NgRx Store is a reactive state management library for Angular modeled after Redux. It consolidates application state into a single, global, immutable object called the **Store**. Components interact with the Store in a strictly unidirectional loop: they dispatch **Actions** describing events; pure **Reducers** capture these actions to compute the next state reference; and components consume the updated data using query functions called **Selectors**. External side-effects are isolated within **Effects**.
3. Why This Topic Matters
NgRx provides predictability and debugging control for complex client architectures:
- State Fragmentation: As applications grow, sharing states between dozens of nested services and components using scattered BehaviorSubjects becomes difficult to trace. NgRx keeps the entire application state in a single place.
- Time-Travel Debugging: Because state updates occur via dispatched actions, developers can use browser extension tools to step backward and forward in time, inspecting exactly what action modified the state and what the DOM looked like at that moment.
4. Real-World Analogy
Think of NgRx like **a secure Bank Ledger**:
- The Ledger (The Store): A single book listing all balances. Employees cannot edit these numbers directly.
- The Deposit Slip (Action): You fill out a slip that reads "Deposit $100". You do not walk behind the counter and add money to the drawer yourself; you hand the slip to the teller.
- The Teller (Reducer): Reads the slip, verifies it, and writes a new row in the ledger recalculating the balance. Tellers are pure workers; they follow strict rules to calculate new balances.
- The Bank Auditor (Selector): Reads specific ledger columns (e.g. checking account balance) to show you how much cash you have without scanning every page in the book.
- The Armored Car (Effect): Handles tasks outside the ledger room, like fetching cash from the federal vault (calling an HTTP API) and bringing back a deposit slip when it arrives.
5. Core Concepts
NgRx relies on distinct, isolated concepts working in a unidirectional loop:
| Element | Purity | Role |
|---|---|---|
| Action | Pure Payload | Unique event identifiers containing associated data payloads. |
| Reducer | 100% Pure | Function that takes current state and action, returning a new state reference. |
| Selector | 100% Pure | Memoized query that filters and extracts specific state slices. |
| Effect | Side Effectful | Listens to actions, runs async tasks (like HTTP calls), and dispatches new actions on finish. |
6. Syntax & API Reference
Below is the syntax to define actions, reducers, selectors, and effects inside an Angular state slice:
7. Visual Diagram
The diagram below tracks the unidirectional flow of NgRx state loops:
8. Live Example — Full Working Code
Below is a complete implementation of a user directory system, utilizing NgRx Store and Effects to load user listings from an HTTP API:
What just happened? Component initialization dispatches the loadUsers action. The usersReducer intercepts it to set `loading = true`. Concurrently, UsersEffects intercepts it, triggers a mock HTTP call, and dispatches a loadUsersSuccess action with the results. The reducer catches the success action, saves the list to state, and updates the template reactively through selectors.
9. Interactive Playground
Try It Yourself Challenges:
- Add a
deleteUseraction. Write an updater case inside the reducer that filters out the deleted user ID from the user list. - Install the Redux DevTools extension. Verify that you can trigger, pause, and inspect actions like
[Users API] Load Usersin real-time.
10. Common Mistakes
| Mistake | Why it happens | Wrong | Correct |
|---|---|---|---|
| Mutating State in Reducers | Directly modifying properties of the existing state object reference instead of returning a new state reference. Breaks change detection. | state.count = 2; |
return { |
| Omitting ofType in Effects | Failing to filter actions in effects causes the effect stream to run on *every* action in the app, causing infinite execution loops. | this.actions$.pipe( |
this.actions$.pipe( |
11. Best Practices
- Keep Reducers pure: Reducers must be pure functions with zero side-effects. Do not perform API calls, calculate random numbers, or read timestamps inside them.
- Use createActionGroup: Group actions by their source component or API using
createActionGroupto reduce boilerplate. - Write memoized selectors: Query state slices exclusively using
createSelectorto leverage automatic caching and avoid redundant rendering recalculations. - Isolate side-effects: All HTTP network calls, local storage operations, and browser notifications belong in NgRx Effects.
12. Browser Compatibility/Requirements
NgRx relies on RxJS operators and ES6 JavaScript features. It runs consistently in all modern web browsers.
13. Interview Questions
Q1: What does it mean that reducers must be pure functions, and why is this requirement critical in NgRx?
Answer: A pure function always returns the same output for the same input and has zero side-effects (e.g. no API calls or globals mutations). Reducers must be pure because NgRx uses these returns to compare old and new state references (`oldState === newState`). If you mutate properties directly without changing the object reference, Angular cannot detect the change, rendering the UI unresponsive.
Q2: How do NgRx Effects help maintain clean component code?
Answer: Effects isolate asynchronous side-effects (like HTTP calls or storage syncs) from components and reducers. Without effects, components must inject services, subscribe to HTTP streams, format responses, and manually handle errors. With effects, the component simply dispatches an action. The effect handles the API call in the background and dispatches a success or failure action, keeping components lean and focused only on visual rendering.
14. Debugging Exercise
Identify why the application crashes with an "Infinite loop detected" error upon dispatching the user load action:
Diagnosis: The effect intercepts the loadUsers action, fetches user data, and then maps the result to dispatch the *same* loadUsers action. This triggers the effect again, launching another HTTP request and dispatching loadUsers in an infinite loop.
Fix: Map the successful API output to dispatch a separate action like loadUsersSuccess.
15. Practice Exercises
Exercise 1: Build a global shopping cart store
Implement an NgRx store slice for managing a shopping cart. Create actions for addItem, removeItem, and clearCart. Write reducer logic to handle these actions immutably, and create a selector to calculate the total price of all items in the cart.
16. Scenario-Based Challenge
The Offline Sync Feature Challenge:
You are designing a notes application that must support offline usage. When a user creates a note, it should save to the store. If the device goes offline, queue the updates. Design an NgRx Effect that detects changes in network status, synchronizes offline notes with a backend API when connectivity is restored, and updates the local store states with the server response.
17. Quick Quiz
Q1: Which NgRx function is used to create a query query that extracts and caches a slice of the global state?
A) select()
B) createSelector()
C) selectFeature()
Answer: B — createSelector() defines a memoized selector query function.
18. Summary & Key Takeaways
- NgRx consolidates application state into a single immutable global Store.
- Components trigger updates by dispatching Actions.
- Pure Reducers compute next state references based on incoming Actions.
- Selectors query and cache state slices, and Effects manage asynchronous side-effects.
19. Cheat Sheet
| API / Concept | Purpose |
|---|---|
store.dispatch(action) |
Fires an action to trigger state updates or effects. |
createReducer(init, on()) |
Creates a reducer function containing transition rules. |
createEffect(() => ...) |
Defines an effect listener to handle asynchronous actions. |