Sidenav Reducer
With the actions declared for our sidenav, our next step is to create the reducer. If you recall, the reducer:
- Is a pure function.
- Accepts the current state and an action that describes the state change.
- Returns a new state object (never mutates the existing state object).
Create a new file src/app/state/sidenav/sidenav.reducer.ts.
State Interface
The State interface defines the properties for the sidenav slice of state:
export interface State {
opened: boolean;
}We have defined a single opened property, which is a boolean value indicating if the sidenav is open or not.
Initial State
With the State interface defined, our next step is to declare the initial state.
The initial state will be the default value for the state of the sidenav.
Initially, we want the sidenav to be hidden, or not open.
export const initialState: State = {
opened: false
};- Declare the
initialStateconstant. - The object is declared with the
Statetype to ensure that theinitialStatemeets the contract for our state object. - We set the
openedinitial value tofalse.
Reducer Function
Finally, create a new reducer() function that accepts the current state and an action that is dispatched:
import { SidenavAction, SidenavActionTypes } from './sidenav.actions';
export function reducer(state = initialState, action: SidenavAction): State {
switch (action.type) {
case SidenavActionTypes.HideSidenav:
return {
...state,
opened: false
};
case SidenavActionTypes.ShowSidenav:
return {
...state,
opened: true
};
default:
return state;
}
}- The
reducer()function accepts the currentstate, which defaults to theinitialStatewe previously declared. - The
reducer()function accepts anaction, which is aSidenavAction. By using the type union we can take advantage of the TypeScript compiler to ensure that we reference the appropriate properties declared within eachActionclass. - We switch on the
typestring value for theactionthat is dispatched. - When the
SidenavActionTypes.HideSidenavaction is dispatched we return the new state of the application. Using the spread operator we can create a shallow clone of the currentstateand modify theopenedproperty to befalse. - When the
SidenavActionTypes.ShowSidenavaction is dispatched we return the new state with theopenedproperty set totrue. - When the
actiondoesn't meet any of the switch cases, we return the currentstate.
Pro Tip: Don't forget to include the default where we return the current state. Omitting this will cause the state object to be undefined.
What about tests?
Let's focus on learning the NgRx store library implementation then we'll learn how to write unit tests for our reducers and effects.
Declare Top-Level State Property
Open the src/app/state/index.ts file and add a new sidenav property to the State interface:
import { State as SidenavState } from './sidenav/sidenav.reducer';
export interface State {
sidenav: SidenavState;
}Pro Tip: Note that I used an alias for the imported State interface from the sidenav.reducer module to avoid naming conflicts.
Another option is to import all exported members using the wildcard ( * ) syntax.
I prefer to simply use an aliased import so as not to import all exported members from the module.
Declare Reducer
You may notice that your editor indicates that there is now an error with the reducers object, which is defined as an ActionReducerMap of the State interface.
This is because the ActionReducerMap interface requires that the reducersobject have keys that match the State interface.
Since we just added a new sidenav property to State we also need to create a sidenav property whose value is the reducer() function we declared.
Open src/app/state/index.ts and add a new sidenav property to the reducers map:
import { ActionReducerMap, MetaReducer } from '@ngrx/store';
import {
reducer as sidenavReducer,
State as SidenavState
} from './sidenav/sidenav.reducer';
export const reducers: ActionReducerMap<State> = {
sidenav: sidenavReducer
};- We have now declare a new top-level property within the state object, often referred to as "feature" state.
- We have also wired up the
reducer()function to mutate the feature state by returning a new state object when an action is dispatched.
Using Features Enum
TypeScript version 2.4.0 introduced string enums.
If you are using Angular 5 or greater then we can define the sidenav property using an enum.
Open src/app/state/index.ts and declare a Features enum:
export enum Features {
sidenav = 'sidenav'
}Then, update the State interface and reducers map to use the enum value:
export interface State {
[Features.sidenav]: SidenavState;
}
export const reducers: ActionReducerMap<State> = {
[Features.sidenav]: sidenavReducer
};Pro Tip: This provides little to no value now as the ActionReducerMap interface ensures the map contains the same keys as the State interface.
However, we'll see the advantage to use the enum when we declare the top-level state property using createFeatureSelector().
Serve the Application
Execute either command to serve the application on http://localhost:4200
npm start
yarn startOpen the Chrome or Firefox developer tools and select the Redux tab. You should now see the initial state of the application:
- Note that I selected the "State" tab to show the full state object.
- The
sidenavtop-level state property contains theopenedboolean value, which defaults tofalse.