0
0
Angularframework~15 mins

Actions and reducers pattern in Angular - Deep Dive

Choose your learning style9 modes available
Overview - Actions and reducers pattern
What is it?
The actions and reducers pattern is a way to manage state in Angular applications. Actions are simple messages that describe what happened, and reducers are functions that decide how the state changes based on those actions. This pattern helps keep the app's data organized and predictable.
Why it matters
Without this pattern, managing state in an app can become messy and confusing, especially as the app grows. Changes might happen in unexpected ways, making bugs hard to find. Using actions and reducers makes state changes clear and easy to follow, improving app reliability and developer confidence.
Where it fits
Before learning this, you should understand basic Angular concepts like components and services. After mastering actions and reducers, you can explore advanced state management libraries like NgRx or Akita, which build on this pattern to handle complex app states.
Mental Model
Core Idea
Actions describe what happened, reducers decide how the state changes in response, keeping state predictable and easy to manage.
Think of it like...
Imagine a mailroom where actions are letters describing requests, and reducers are clerks who read these letters and update the records accordingly.
┌─────────┐     ┌───────────┐     ┌─────────────┐
│  Action │ ──▶ │ Reducer   │ ──▶ │ New State   │
└─────────┘     └───────────┘     └─────────────┘
Build-Up - 7 Steps
1
FoundationUnderstanding Application State
🤔
Concept: Learn what application state means and why it needs management.
Application state is all the data your app uses to show information and respond to user actions. For example, a shopping cart's items or a user's login status are parts of state. Managing this state means keeping it accurate and updated as users interact with the app.
Result
You know what state is and why it matters in apps.
Understanding state is the foundation for managing how your app behaves and looks.
2
FoundationIntroducing Actions as Event Descriptions
🤔
Concept: Actions are simple objects that describe what happened in the app.
An action usually has a type (a string describing the event) and may carry extra data (called payload). For example, an action could be { type: 'ADD_ITEM', payload: { id: 1, name: 'Book' } }. Actions do not change state themselves; they just say what happened.
Result
You can identify and create actions that describe user or system events.
Actions separate the description of events from how the app reacts, making changes easier to track.
3
IntermediateReducers: Pure Functions Updating State
🤔Before reading on: do you think reducers can modify the original state object or must they create a new one? Commit to your answer.
Concept: Reducers are functions that take the current state and an action, then return a new state without changing the old one.
Reducers look like function(state, action) { ... }. They check the action type and decide how to update the state. Importantly, they never change the original state directly but return a new updated copy. This keeps state predictable and avoids bugs.
Result
You understand how reducers update state safely and predictably.
Knowing reducers are pure functions helps prevent accidental state changes that cause hard-to-find bugs.
4
IntermediateConnecting Actions and Reducers in Angular
🤔Before reading on: do you think Angular automatically links actions to reducers, or do you need to set this up explicitly? Commit to your answer.
Concept: In Angular, you explicitly dispatch actions and use reducers to handle them, often with libraries like NgRx.
You dispatch actions using a store service, like store.dispatch({ type: 'ADD_ITEM', payload: item }). The store then calls the reducer with the current state and this action. The reducer returns the new state, which updates the app's data. This flow keeps state changes clear and centralized.
Result
You see how Angular apps use actions and reducers together to manage state.
Explicitly connecting actions and reducers makes state changes transparent and easier to debug.
5
IntermediateHandling Multiple Actions in One Reducer
🤔Before reading on: do you think one reducer handles only one action type or can handle many? Commit to your answer.
Concept: Reducers often handle many action types using conditional logic or switch statements.
A reducer function checks the action type and runs different code for each. For example, it might add an item for 'ADD_ITEM' or remove one for 'REMOVE_ITEM'. This keeps all related state changes in one place, making the code organized.
Result
You can write reducers that respond to multiple actions cleanly.
Grouping related state changes in one reducer improves maintainability and clarity.
6
AdvancedImmutable State Updates in Reducers
🤔Before reading on: do you think you can modify nested objects directly in reducers or should you create new copies? Commit to your answer.
Concept: Reducers must update state immutably, creating new copies of changed parts instead of modifying originals.
For example, to add an item to a list in state, you create a new list with the item added instead of pushing into the existing list. This prevents bugs and helps Angular detect changes for UI updates.
Result
You know how to write reducers that update state immutably.
Immutable updates ensure Angular's change detection works correctly and avoid subtle bugs.
7
ExpertOptimizing Reducers for Performance
🤔Before reading on: do you think reducers should always create new state objects even if nothing changed? Commit to your answer.
Concept: Reducers should return the original state if no changes happen to avoid unnecessary UI updates.
If a reducer receives an action that doesn't affect the state, it should return the existing state object. This optimization helps Angular detect when to re-render components efficiently, improving app performance.
Result
You can write reducers that avoid unnecessary state updates.
Returning unchanged state objects prevents wasted work and keeps apps fast.
Under the Hood
When an action is dispatched, Angular's store calls the reducer with the current state and the action. The reducer runs synchronously, returning a new state object. Angular's change detection compares the old and new state references to decide if UI updates are needed. This process ensures state changes are predictable and traceable.
Why designed this way?
This pattern was designed to solve the chaos of scattered state changes in large apps. By centralizing state updates in pure functions and describing changes as actions, it became easier to debug, test, and reason about app behavior. Alternatives like direct state mutation were error-prone and hard to maintain.
┌───────────────┐       ┌───────────────┐       ┌───────────────┐
│ Dispatch      │──────▶│ Reducer       │──────▶│ New State     │
│ Action Object │       │ Pure Function │       │ Immutable     │
└───────────────┘       └───────────────┘       └───────────────┘
         │                      │                       │
         ▼                      ▼                       ▼
  ┌─────────────┐        ┌─────────────┐         ┌─────────────┐
  │ Store       │◀───────│ Previous    │◀────────│ UI          │
  │ Notifies UI │        │ State       │         │ Updates     │
  └─────────────┘        └─────────────┘         └─────────────┘
Myth Busters - 4 Common Misconceptions
Quick: Do reducers modify the existing state object directly? Commit to yes or no.
Common Belief:Reducers change the existing state object directly to update it.
Tap to reveal reality
Reality:Reducers must never modify the existing state; they always return a new state object.
Why it matters:Modifying state directly breaks Angular's change detection, causing UI bugs and unpredictable behavior.
Quick: Is it okay to dispatch actions anywhere in the app without structure? Commit to yes or no.
Common Belief:You can dispatch actions from anywhere without organizing them.
Tap to reveal reality
Reality:Actions should be dispatched in a controlled way, often from components or effects, to keep state changes predictable.
Why it matters:Unstructured dispatching leads to hard-to-trace bugs and inconsistent state.
Quick: Do reducers handle asynchronous tasks like API calls? Commit to yes or no.
Common Belief:Reducers can perform asynchronous operations like fetching data.
Tap to reveal reality
Reality:Reducers are pure and synchronous; asynchronous tasks belong in effects or services.
Why it matters:Mixing async logic in reducers breaks purity, making state unpredictable and hard to test.
Quick: Does returning a new state object always mean the state changed? Commit to yes or no.
Common Belief:Returning a new state object always means the state has changed.
Tap to reveal reality
Reality:Reducers should return the original state if nothing changed to avoid unnecessary UI updates.
Why it matters:Creating new objects unnecessarily causes performance issues and extra rendering.
Expert Zone
1
Reducers must be pure functions without side effects to keep state predictable and testable.
2
Using action creators and typed actions improves code safety and developer experience in Angular.
3
Combining multiple reducers into one root reducer helps organize complex state logically.
When NOT to use
For very simple apps, this pattern might add unnecessary complexity; simpler state management like local component state or services may suffice. Also, for highly dynamic or real-time data, specialized libraries or patterns like Observables with BehaviorSubjects might be better.
Production Patterns
In real Angular apps, actions and reducers are often used with NgRx, which adds effects for async tasks and selectors for efficient state reading. Developers split state into feature slices with separate reducers and use action creators for type safety and clarity.
Connections
Event-driven architecture
Builds-on
Actions in this pattern are like events in event-driven systems, helping apps react to changes in a clear, decoupled way.
Functional programming
Shares principles
Reducers are pure functions, a core idea in functional programming that helps avoid bugs by not changing inputs.
Accounting double-entry bookkeeping
Similar pattern
Just like bookkeeping records every transaction clearly to keep accounts balanced, actions and reducers record every state change explicitly to keep app data consistent.
Common Pitfalls
#1Modifying state directly inside a reducer.
Wrong approach:function reducer(state, action) { if (action.type === 'ADD_ITEM') { state.items.push(action.payload); return state; } return state; }
Correct approach:function reducer(state, action) { if (action.type === 'ADD_ITEM') { return { ...state, items: [...state.items, action.payload] }; } return state; }
Root cause:Misunderstanding that state must be immutable and that direct changes break Angular's change detection.
#2Performing asynchronous API calls inside reducers.
Wrong approach:function reducer(state, action) { if (action.type === 'FETCH_DATA') { fetch('/api/data').then(response => { // update state here }); return state; } return state; }
Correct approach:Use effects or services to handle async calls, then dispatch actions with results for reducers to handle.
Root cause:Confusing reducers as places for all logic instead of pure state updates.
#3Returning a new state object even when no changes happened.
Wrong approach:function reducer(state, action) { switch(action.type) { default: return { ...state }; } }
Correct approach:function reducer(state, action) { switch(action.type) { default: return state; } }
Root cause:Not realizing that returning a new object triggers unnecessary UI updates and hurts performance.
Key Takeaways
Actions and reducers separate what happened from how state changes, making app data predictable and easier to manage.
Reducers must be pure and update state immutably to keep Angular's UI in sync and avoid bugs.
Dispatching actions explicitly and handling them in reducers centralizes state changes for clarity and debugging.
Returning the original state when no changes occur prevents unnecessary UI updates and improves performance.
This pattern scales well for complex apps and forms the foundation for advanced Angular state management libraries.