Create a Reducer page
Learn how to create an NgRx Reducer.
Quick Start: You can checkout this branch to get your codebase ready to work on this section.
Overview
Define Login State shape.
Set Login initial state.
Update Reducer function to include handler for
LoginActions.loginSuccess
Action to update Login State.Update Reducer function to include handler for
LoginActions.logoutSuccess
Action to reset Login State.
Problem 1: Define Login State Shape
Update the Login State shape by adding userId
, username
, and token
properties to its interface definition. Each property should have type string | null
.
P1: What you need to know
Now that we have all of our Actions prepared to dispatch whenever we need, we will update the Login State. Typically a Reducer is updated with 3 steps:
Define or update
Store
interface to have an expected shape after Reducer updates state.Set or update value to state’s initial value to satisfy new
Store
interface definition.Add or update handler(s) used to define Reducer function.
To prepare this, we need to update the Login Feature State
interface found at src/app/store/login/login.reducer.ts
. Here is an example of how to add properties to an interface:
// Note: This example code is not part of our application repo or solution
export interface Contact {
emailAddress: string | null;
fullName: string | null;
}
P1: Solution
src/app/store/login/login.reducer.ts
// src/app/store/login/login.reducer.ts
import { createReducer } from '@ngrx/store';
export const loginFeatureKey = 'login';
export interface State {
userId: string | null;
username: string | null;
token: string | null;
}
export interface LoginPartialState {
[loginFeatureKey]: State;
}
export const initialState: State = {
};
export const reducer = createReducer(
initialState
);
Problem 2: Set Initial Value For Login State
Set initial state for Login State. Each member of the Login State should start with null
as its value.
P2: What you need to know
Now that we have updated the Login State’s shape by updating the State
interface, we need to update its initial shape. By default, we will set each member to null
. Here is an example of doing that:
export const initialValue: Contact = {
emailAddress: null;
fullName: null;
}
P2: Solution
src/app/store/login/login.reducer.ts
// src/app/store/login/login.reducer.ts
import { createReducer } from '@ngrx/store';
export const loginFeatureKey = 'login';
export interface State {
userId: string | null;
username: string | null;
token: string | null;
}
export interface LoginPartialState {
[loginFeatureKey]: State;
}
export const initialState: State = {
userId: null,
username: null,
token: null,
};
export const reducer = createReducer(
initialState
);
Problem 3: Update Login State With Login Information On Login
Login Reducer should include an on()
handler that updates Login State with userId
, username
and token
whenever LoginActions.loginSuccess
Action is dispatched.
P3: What you need to know
We can create NgRx Reducers using the createReducer()
helper function.
The first argument for createReducer()
sets the initial value of your State. Then every argument after should be an on()
handler function calls.
When writing an on()
handler, there are 2 arguments that we need to provide:
The Action that this
on()
handler is reacting to.A pure function that takes in 2 arguments:
state
andaction
. This function should always return a new state that will replace the previous state.
// Note: This example code is not part of our application repo or solution
import { createReducer, on } from '@ngrx/store';
import * as CounterActions from './counter.actions';
export const scoreboardReducer = createReducer(
initialState,// Initial feature state
on(CounterActions.set, (state, action) => ({ ...state, count: action.count })),// Set counter
on(CounterActions.increment, state => ({ ...state, count: state.count + 1 })),// Increment counter
// ...
);
P3: Solution
src/app/store/login/login.reducer.ts
// src/app/store/login/login.reducer.ts
import { createReducer, on } from '@ngrx/store';
import * as LoginActions from './login.actions';
export const loginFeatureKey = 'login';
export interface State {
userId: string | null;
username: string | null;
token: string | null;
}
export interface LoginPartialState {
[loginFeatureKey]: State;
}
export const initialState: State = {
userId: null,
username: null,
token: null,
};
export const reducer = createReducer(
initialState,
on(
LoginActions.loginSuccess,
(state, { userId, username, token }): State => ({
...state,
userId,
username,
token,
})
)
);
Problem 4: Reset Login State On Logout
Login Reducer should include an on()
handler that resets Login State back to initialState
whenever LoginActions.logoutSuccess
Action is dispatched.
P4: What you need to know
A common requirement is to reset state. One approach might look like this:
// Note: This example code is not part of our application repo or solution
import { createReducer, on } from '@ngrx/store';
import * as CounterActions from './counter.actions';
export const scoreboardReducer = createReducer(
initialState,// Initial feature state
on(CounterActions.set, (state, action) => ({ ...state, count: action.count })),// Set counter
on(CounterActions.increment, state => ({ ...state, count: state.count + 1 })),// Increment counter
on(CounterActions.reset, (state) => ({ ...state, count: 0 })),// Reset counter
// ...
);
But there is nothing wrong with reusing the initialState
constant:
// Note: This example code is not part of our application repo or solution
import { createReducer, on } from '@ngrx/store';
import * as CounterActions from './counter.actions';
export const scoreboardReducer = createReducer(
initialState,// Initial feature state
on(CounterActions.set, (state, action) => ({ ...state, count: action.count })),// Set counter
on(CounterActions.increment, state => ({ ...state, count: state.count + 1 })),// Increment counter
on(CounterActions.reset, () => ({ ...initialState })),// Reset counter
// ...
);
Both solutions are fine, but it is likely better to reuse initialState
to future-proof the Reducer for future requirements.
P4: Solution
src/app/store/login/login.reducer.ts
// src/app/store/login/login.reducer.ts
import { createReducer, on } from '@ngrx/store';
import * as LoginActions from './login.actions';
export const loginFeatureKey = 'login';
export interface State {
userId: string | null;
username: string | null;
token: string | null;
}
export interface LoginPartialState {
[loginFeatureKey]: State;
}
export const initialState: State = {
userId: null,
username: null,
token: null,
};
export const reducer = createReducer(
initialState,
on(
LoginActions.loginSuccess,
(state, { userId, username, token }): State => ({
...state,
userId,
username,
token,
})
),
on(LoginActions.logoutSuccess, (): State => ({ ...initialState }))
);
Wrap-up: By the end of this section, your code should match this branch. You can also compare the code changes for our solution to this section on GitHub or you can use the following command in your terminal:
git diff origin/create-reducer