Angular
  • Angular learning
  • Angular
    • Change Detection
      • Angular Change Detection Strategies
      • Understanding Change Detection Strategy in Angular
    • Angular Components Overview
      • Lifecycle hooks
      • View encapsulation
    • Text interpolation
    • Pipes
    • ARIA
    • Event binding
    • Directives
    • Dependency injection in Angular
    • Difference between Template-Driven and Reactive Forms
    • Guards
    • Resolvers
      • Resolver example
  • Memory management in Angular applications
  • Renderer2
  • Angular test
    • Testing
      • The different types of tests
      • Some testing best practices
      • Angular Service Testing in Depth
        • About Jasmine
        • Jasmine - Test suites
        • Implementation of First Jasmine Specfication
        • spyOn() & jasmine.createSpyObj()
        • beforeEach()
        • Testing services
        • Disabled and Focused Tests
        • flush
        • HttpTestingController
        • Sample code
      • Angular Component Testing in Depth
        • Intro to Angular Component testing
        • DOM interaction
        • Trigger Change Detection
        • Test Card List Test Suite Conclusion
        • Window.requestAnimationFrame()
        • Asynchronous Work (Jasmine)
        • Cooperative asynchronous JavaScript: Timeouts and intervals
        • FakeAsync - Asynchronous Work (Jasmine) part 2
        • tick()
        • Sample codes
      • Testing Promised-based code-intro Microtasks
        • Microtasks
        • Micro-tasks within an event loop (Summary)
        • Macro-tasks within an event loop (Summary)
        • Test promised Microtasks (code)
      • Using fakeAsync to test Async Observables
      • Cypress.io
        • Create our first e2e test
      • Angular CLI code coverage and deployment in prod mode.
      • Travis CI
  • Angular best practices
    • Angular best practices
      • Security
      • Accessibility in Angular
      • Keeping your Angular projects up-to-date
    • Bootstrapping an Angular Application
      • Understanding the File Structure
      • Bootstrapping Providers
    • Components in Angular
      • Creating Components
      • Application Structure with Components
        • Accessing Child Components from Template
        • Using Two-Way Data Binding
        • Responding to Component Events
        • Passing Data into a Component
      • Projection
      • Structuring Applications with Components
      • Using Other Components
  • Reactive extensions
    • RxJS
      • RxJS Operators
      • of
      • Observable
      • async pipe (Angular)
      • Interval
      • fromEvent
      • Pipe
      • Map
      • Tap
      • ShareReplay
      • Concat
      • ConcatMap
      • Merge
      • MergeMap
      • ExhaustMap
      • fromEvent
      • DebounceTime
        • Type Ahead
      • Distinct Until Changed
      • SwitchMap
      • CatchError
      • Finalize
      • RetryWhen
      • DelayWhen
      • ForkJoin
      • First
      • Interview Questions
      • Zip
  • NgRx
    • What's NgRx
      • Actions
      • Reducers
      • Selectors
      • 🙅‍♂️Authentication guard with NgRX
      • @ngrx/effects
        • Side-Effect refresh survivor
  • Interview Q&A
    • Angular Unit Testing Interview Questions
    • Angular Questions And Answers
  • Angular Advanced
    • Setting up our environment
      • Understanding Accessors (TS)
      • The host & ::ng-deep Pseudo Selector
Powered by GitBook
On this page
  • Introduction
  • The reducer function
  • Registering root state

Was this helpful?

  1. NgRx
  2. What's NgRx

Reducers

PreviousActionsNextSelectors

Last updated 4 years ago

Was this helpful?

Reducers in NgRx are responsible for handling transitions from one state to the next state in your application. Reducer functions handle these transitions by determining which to handle based on the action's type.

Introduction

Reducers are pure functions in that they produce the same output for a given input. They are without side effects and handle each state transition synchronously. Each reducer function takes the latest dispatched, the current state, and determines whether to return a newly modified state or the original state. This guide shows you how to write reducer functions, register them in your , and compose feature states.

The reducer function

There are a few consistent parts of every piece of state managed by a reducer.

  • An interface or type that defines the shape of the state.

  • The arguments including the initial state or current state and the current action.

  • The functions that handle state changes for their associated action(s).

Below is an example of a set of actions to handle the state of a scoreboard, and the associated reducer function.

First, define some actions for interacting with a piece of state.scoreboard-page.actions.ts

content_copyimport { createAction, props } from '@ngrx/store';

export const homeScore = createAction('[Scoreboard Page] Home Score');
export const awayScore = createAction('[Scoreboard Page] Away Score');
export const resetScore = createAction('[Scoreboard Page] Score Reset');
export const setScores = createAction('[Scoreboard Page] Set Scores', props<{game: Game}>());

Next, create a reducer file that imports the actions and define a shape for the piece of state.

Defining the state shape

Each reducer function is a listener of actions. The scoreboard actions defined above describe the possible transitions handled by the reducer. Import multiple sets of actions to handle additional state transitions within a reducer.scoreboard.reducer.ts

content_copyimport { Action, createReducer, on } from '@ngrx/store';
import * as ScoreboardPageActions from '../actions/scoreboard-page.actions';

export interface State {
  home: number;
  away: number;
}

You define the shape of the state according to what you are capturing, whether it be a single type such as a number, or a more complex object with multiple properties.

Setting the initial state

The initial state gives the state an initial value, or provides a value if the current state is undefined. You set the initial state with defaults for your required state properties.

Create and export a variable to capture the initial state with one or more default values.scoreboard.reducer.ts

content_copyexport const initialState: State = {
  home: 0,
  away: 0,
};

The initial values for the home and away properties of the state are 0.

Creating the reducer function

content_copyconst scoreboardReducer = createReducer(
  initialState,
  on(ScoreboardPageActions.homeScore, state => ({ ...state, home: state.home + 1 })),
  on(ScoreboardPageActions.awayScore, state => ({ ...state, away: state.away + 1 })),
  on(ScoreboardPageActions.resetScore, state => ({ home: 0, away: 0 })),
  on(ScoreboardPageActions.setScores, (state, { game }) => ({ home: game.home, away: game.away }))
);

export function reducer(state: State | undefined, action: Action) {
  return scoreboardReducer(state, action);
}

In the example above, the reducer is handling 4 actions: [Scoreboard Page] Home Score, [Scoreboard Page] Away Score, [Scoreboard Page] Score Reset and [Scoreboard Page] Set Scores. Each action is strongly-typed. Each action handles the state transition immutably. This means that the state transitions are not modifying the original state, but are returning a new state object using the spread operator. The spread syntax copies the properties from the current state into the object, creating a new reference. This ensures that a new state is produced with each change, preserving the purity of the change. This also promotes referential integrity, guaranteeing that the old reference was discarded when a state change occurred.

Registering root state

content_copyimport { NgModule } from '@angular/core';
import { StoreModule } from '@ngrx/store';
import * as fromScoreboard from './reducers/scoreboard.reducer';

@NgModule({
  imports: [
    StoreModule.forRoot({ game: fromScoreboard.reducer })
  ],
})
export class AppModule {}

The reducer function's responsibility is to handle the state transitions in an immutable way. Create a reducer function that handles the actions for managing the state of the scoreboard using the function.scoreboard.reducer.ts

Note: The exported function is necessary as the View Engine AOT compiler. It is no longer required if you use the default Ivy AOT compiler (or JIT).

Note: The only does shallow copying and does not handle deeply nested objects. You need to copy each level in the object to ensure immutability. There are libraries that handle deep copying including and .

When an action is dispatched, all registered reducers receive the action. Whether they handle the action is determined by the functions that associate one or more actions with a given state change.

Note: You can also write reducers using switch statements, which was the previously defined way before reducer creators were introduced in NgRx. If you are looking for examples of reducers using switch statements, visit the documentation for .

The state of your application is defined as one large object. Registering reducer functions to manage parts of your state only defines keys with associated values in the object. To register the global within your application, use the method with a map of key/value pairs that define your state. The registers the global providers for your application, including the service you inject into your components and services to dispatch actions and select pieces of state.app.module.ts

Registering states with ensures that the states are defined upon application startup. In general, you register root states that always need to be available to all areas of your application immediately.

actions
Action
Store
createReducer
reducer
function calls are not supported
spread operator
lodash
immer
on
versions 7.x and prior
Store
StoreModule.forRoot()
StoreModule.forRoot()
Store
StoreModule.forRoot()