NgRx Add State to Feature Module

by | Sep 5, 2018

Feature modules are Angular modules that group a bunch of components/services, etc. into a module.  This allows us to separate concerns nicely.  Most likely, you’ve dealt with feature modules if you have 1 month experience in Angular.  Since feature modules can be lazy loaded and because they are separate from the main app module, NgRx does things a little differently.  In this post, I plan to describe how to setup a lazy loaded feature module with NgRx.

The code is on github.

Before we jump in, here are other NgRx tutorials I’ve written:

 

Create a Feature Module

Staying with the Star Wars API theme, I’ll create a “Starships” feature module that will display a list of the first ten starships in the Star Wars universe.

This is the Angular CLI command to create the module:

ng g module starships

This creates the following module code:

@NgModule({
  imports: [
    CommonModule
  ],
  declarations: []
})
export class StarshipsModule { }

Create the NgRx Feature Module Code

Before creating the component in the module to show the starships, I’m going to generate the NgRx actions/effects/reducers using the following NgRx Schematics command:

ng generate feature starships/Ships -m starships/starships.module.ts --group --spec false

This does the following:

  • Creates actions, effects and reducers folders with their respective files
    • ships.actions.ts, ships.effects.ts, ships.reducer.ts
  • Updates the starships module we created first thing as shown:
@NgModule({
  imports: [
    CommonModule,
    StoreModule.forFeature('starships', fromShips.reducer),
    EffectsModule.forFeature([ShipsEffects])
  ],
  declarations: []
})
export class StarshipsModule { }

The following were added:

  • StoreModule.forFeature(‘starships’, fromShips.reducer)
  • EffectsModule.forFeature([ShipsEffects])

So you can see that our feature module has it’s own module setup but “forFeature” instead of “forRoot” as in the app.module.

Also note that instrumentation wasn’t setup in the feature module, that is only required in the root module (app.module).  For a refresher, this is the setup of instrumentation in the app.module imports:

!environment.production ? StoreDevtoolsModule.instrument() : []

This gives us the ability to view state from a Chrome browser extension called Redux DevTools.

I’m also going to move the actions, effects and reducers folders under a “store” folder.  I do this so it’s easier to find all things NgRx within the feature module.

Last thing is to fix up the actions file because this is how it was generated:

import { Action } from '@ngrx/store';

export enum ShipsActionTypes {
  LoadShipss = '[Ships] Load Shipss'
}

export class Ships implements Action {
  readonly type = ShipsActionTypes.LoadShipss;
}

export type ShipsActions = LoadShipss;

As you can see there are some problems here.

  • LoadShipss – problem with plurality
  • Ships action should be LoadShips

 

Reorganize the Starships Reducer

The way it’s setup now, the ships reducer is setup to handle only one reducer in the feature module.  That’s obviously not ideal so a little reorganization is in order.

First, I add some state to the ships reducer:

export interface State {
  allShips: any[];
}

const initialState: State = {
  allShips: []
};

Now I’m going to create an index.ts file in the starships/store/reducers folder:

import {
  createSelector,
  createFeatureSelector,
  ActionReducerMap,
} from '@ngrx/store';

import * as fromShips from './ships.reducer';
import * as fromRoot from '../../../store/reducers';

export interface StarshipsState {
  ships: fromShips.State;
}

export interface State extends fromRoot.State {
  ships: StarshipsState;
}

export const reducers: ActionReducerMap<StarshipsState> = {
  ships: fromShips.reducer
};

These are the important points:

  • Created interface StarshipsState – this will hold all of the different states in the different reducers we may create in this feature module
  • Created an ActionReducerMap of StarshipsState – this will define the reducers that correspond to the properties in StarshipsState
  • Created interface State that extends root or main State – this is the one that will be referred to in our components in this feature module and will allow us to see state from the app.module level
    • Note you still can’t see state from other feature modules
    • We won’t add any more code to this state as all additional state/reducers will be added to StarshipsState

 

Create Component to Show Starships

Now we are ready to create the component to show the starships and inject the store into the component.

I’ll use this NgRx schematics command to generate a container:

ng generate container starships/ship-list --state store/reducers/index.ts --stateInterface State

This does the following:

  • Creates the ship-list component
    • Injects the store into the component (using our State that extends root State)
  • Adds the component to our starships.module declaration

Here is the HTML for the component:

<h1>{{user$ | async}}</h1>
<hr/>
<h2>First 10 Starships from API</h2>
<table>
  <thead>
    <tr>
      <th>Name</th>
      <th>Model</th>
    </tr>
  </thead>
  <tbody>
    <tr *ngFor="let ship of starships$ | async">
      <td style="width:30%">{{ship.name}}</td>
      <td style="width:60%">{{ship.model}}</td>
    </tr>
  </tbody>
</table>
  • The user$ variable is there to use the root part of state that we previously had retrieved (Luke Skywalker)
  • The starships$ variable holds the results of state so initially it will be empty and then it will load once state changes

And then the component code:

export class ShipListComponent implements OnInit {

  starships$: Observable<StarShip[]>;
  user$: Observable<string>;

  constructor(private store: Store<fromStore.State>) { }

  ngOnInit() {
    this.starships$ = this.store.select(fromStore.getAllShips);
    this.user$ = this.store.select(fromRoot.getFriendlyName);

    this.store.dispatch(new LoadShips());
  }
}
  • Dispatch the LoadShips action to load the ships using the Star Wars API in the effect (see code below)
  • Use the fromStore.getAllShips to get the ships from our feature module…here’s how this is setup in the feature module index.ts reducer:
export interface StarshipsState {
  ships: fromShips.State;
}

export interface State extends fromRoot.State {
  starships: StarshipsState;
}

export const reducers: ActionReducerMap<StarshipsState> = {
  ships: fromShips.reducer
};

export const selectStarshipsState = createFeatureSelector<StarshipsState>('starships');

export const selectShips = createSelector(selectStarshipsState, (state) => state.ships);
export const getAllShips = createSelector(selectShips, fromShips.getAllShips);
  • The ‘starships’ in the State interface must match the ‘starships’ in the selectStarshipsState selector
    • And it must match the forFeature declaration in the feature module
  • Note that the “createFeatureSelector” command doesn’t match (as of 8/18/2018) what is in the NgRx example app
    • That’s because it is a future thing that we don’t yet have access to (see Stack Overflow)
  • selectShips gets the ‘ships’ property of StarshipsState
  • getAllShips then just gets the allShips property of fromShips.State

 

LoadShips Action in the Effect

Here is the LoadShips action in the effect (which will call the Star Wars API to get the first ten ships):

  @Effect()
  loadShips$ = this.actions$.pipe(
    ofType(ShipsActionTypes.LoadShips),
    switchMap(() => {
      return this.http.get<any>(`https://swapi.co/api/starships`)
        .pipe(
          map((response) => {
            return new shipActions.SetShips(response.results);
          })
        )
    })
  );
  • SetShips will then call the reducer function to update state as shown in ships.reducer:
export function reducer(state = initialState, action: ShipsActions): State {
  switch (action.type) {

    case ShipsActionTypes.SetShips:
      return handleSetShips(state, action);

    default:
      return state;
  }
}

function handleSetShips(state, action: SetShips): State {
  return {
    ...state,
    allShips: action.payload
  }
}

Redux DevTools to View State

I mentioned Redux DevTools, a Chrome extension, earlier.  Let’s take a look at it now.  It is a useful tool when developing to see what is actually in state and the actions that got it that way.

Here is a screenshot of it for when we view the starships:

  • Notice that we have selected “[Ships] Set Ships”, that’s the action that calls the reducer to change state
    • Load Ships is called before – that one got the ships from the API
  • “starships” is our feature name
  • “ships” is the property on StarshipsState
  • “allShips” is the property on the ships.reducer State

 

How the Page Looks

If you are curious, here is my award-winning web page for showing the starships:

 

Conclusion

So that was quite a lot!  But we were able to do the following:

  1. Add a lazy loaded feature module (see the code on github for how to lazy load a module)
  2. Add NgRx state to it
  3. Prepare for future expansion within the feature module by reorganizing reducer code
  4. Create a cool component using NgRx state
  5. View the contents of the state in the browser using Redux DevTools

After these four tutorials, you should be getting pretty good at using NgRx to manage state in an Angular application.  I’m sure most projects are using feature modules these days and being able to manage state with NgRx is a valuable skill to learn.