Picture of Brian Love wearing black against a dark wall in Portland, OR.

Brian Love

MEAN App: Reactive Programming

Part 3: Let’s build an Angular app using reactive programming with RxJS and ngrx.

Series

This post is part of a series on building a MEAN app using TypeScript with Angular Material and Reactive programming.

Source Code

You can download the source code and follow along or fork the repository on GitHub:

First, run the gulp tasks, then start the Node.js Express server.

$ gulp
$ chmod +x ./dist/bin/www
$ ./dist/bin/www

Then, serve the Angular client using the CLI:

$ ng serve

Goals

Our goals for this series are:

  1. Create a simple CRUD app similar to the Tour of Heros tutorial app for Angular.
  2. Create the REST API using Express written in TypeScript with Mongoose for persisting data to MongoDb.
  3. Use Angular Material and Angular Flex Layout for the UI.
  4. Use Reactive Extensions for JavaScript, specifically, RxJS and ngrx.

Project Structure

In the previous two parts of this series we built our server using Express and Mongoose written in TypeScript. Our TypeScript is transpiled into the root dist directory.

We also started setting things up in the root client directory, which contains our Angular client application. Let’s focus on building our application in the client directory using Angular Material, RxJS and ngrx.

├── client
│   ├── src
│   │   ├── app
│   │   │   ├── app-routing.module.ts
│   │   │   ├── app.component.html
│   │   │   ├── app.component.scss
│   │   │   ├── app.component.ts
│   │   │   ├── app.module.ts
│   │   │   ├── app.reducers.ts
│   │   │   ├── core
│   │   │   │   └── services
│   │   │   │       ├── heros.service.spec.ts
│   │   │   │       └── heros.service.ts
│   │   │   ├── heros
│   │   │   │   ├── heros-routing.module.ts
│   │   │   │   ├── heros.actions.ts
│   │   │   │   ├── heros.effects.ts
│   │   │   │   ├── heros.module.ts
│   │   │   │   ├── heros.reducers.ts
│   │   │   │   └── index
│   │   │   │       ├── index.component.html
│   │   │   │       ├── index.component.scss
│   │   │   │       ├── index.component.spec.ts
│   │   │   │       └── index.component.ts
│   │   │   ├── models
│   │   │   │   └── hero.ts
│   │   │   └── shared
│   │   │       ├── hero-create-dialog
│   │   │       │   ├── hero-create-dialog.component.html
│   │   │       │   ├── hero-create-dialog.component.scss
│   │   │       │   ├── hero-create-dialog.component.spec.ts
│   │   │       │   └── hero-create-dialog.component.ts
│   │   │       ├── heros-list
│   │   │       │   ├── heros-list.component.html
│   │   │       │   ├── heros-list.component.scss
│   │   │       │   ├── heros-list.component.spec.ts
│   │   │       │   └── heros-list.component.ts
│   │   │       ├── layout
│   │   │       │   ├── layout.component.html
│   │   │       │   ├── layout.component.scss
│   │   │       │   ├── layout.component.spec.ts
│   │   │       │   └── layout.component.ts
│   │   │       ├── shared.actions.ts
│   │   │       ├── shared.module.ts
│   │   │       ├── shared.reducers.ts
│   │   │       └── toolbar
│   │   │           ├── toolbar.component.html
│   │   │           ├── toolbar.component.scss
│   │   │           ├── toolbar.component.spec.ts
│   │   │           └── toolbar.component.ts
│   │   ├── environments
│   │   │   ├── environment.prod.ts
│   │   │   └── environment.ts
│   │   ├── index.html
│   │   ├── main.ts
│   │   ├── polyfills.ts
│   │   ├── styles.scss
│   │   └── tsconfig.app.json
│   ├── tsconfig.json
│   └── tslint.json
├── dist
├── gulpfile.js
├── gulpfile.ts
├── package.json
└── server

Review

  1. In the first part of the series we built a REST API using Express and Mongoose.
  2. We also wrote some tests using Mocha and Chai to verify that our API is working as expected.
  3. In the second part of the series we set everything up using Angular, Angular Material and Flex Layout.

In this final part of the series we will:

  1. Install and use RxJS and ngrx to build our application
  2. Refactor the toolbar to use reactive programming with RxJS and ngrx.
  3. Create a shared module with stateless components to make our application more modular.
  4. Create actions and a reducer function for opening and closing the sidebar.
  5. Create actions, effects and a reducer function for creating and removing heros.
  6. Add routing and navigation to our application using Angular’s router tied to the @ngrx/router-store.
  7. Open a dialog to create a hero.
  8. Create a module to list the heros
  9. Include a button to remove a hero.

Install RxJS and ngrx

The first thing we need to do is to install the Reactive Extensions for JavaScript (RxJS) library as well as several ngrx packages via npm:

$ npm install rxjs --save
$ npm install @ngrx/core --save
$ npm install @ngrx/effects --save
$ npm install @ngrx/router-store --save
$ npm install @ngrx/store --save
$ npm install reselect --store
$ npm install ngrx-store-freeze --save

If you are new to using RxJS and ngrx I would recommend that you check out another recent post I wrote where I created a simple authentication example app using RxJS and ngrx. You can also check out their documentation and more on their respective websites:

Refactor Toolbar

In the previous part of this series we installed and configured the Angular Material MdToolbarModule module. We also updated the client/src/app/app.component.html template to include a toolbar along with the title of our application. Rather than continuing to build our application in a single HTML template, or module, we will start to modularize our application. The first step is to move our toolbar into it’s own module.

I like to create a shared module for common modules, such as the toolbar, that will be used by our application. We can use the Angular CLI to generate the module for us:

$ ng g m shared

Let me explain what the command above does:

When we generate the module the CLI tells us that the module was created, but it provides us with a warning that we must provide the newly created module ourselves. To do this, import the module into the client/src/app/app.module.ts file and then add the module to the array of imports in the AppModule decorator:

import { SharedModule } from './shared/shared.module';

@NgModule({
  declarations: [AppComponent],
  imports: [BrowserAnimationsModule, BrowserModule, SharedModule],
  providers: [],
  bootstrap: [AppComponent],
})
export class AppModule {}

Note that the only item we added to the imports property above was the SharedModule module.

When you generated the SharedModule, a new client/src/app/shared directory was created, along with a new file at client/src/app/shared/shared.module.ts. After we generate components in our shared module we will declare them in this module file. We will also likely use the exports property to export components out of this module and into other modules in our application.

Let’s go ahead and create a new ToolbarComponent component:

$ ng g c shared/toolbar

Let me break down the command again:

Jump back to the client/src/app/shared/shared.module.ts file and modify the declarations property in the decorator for our SharedModule class to include the ToolbarComponent:

import { ToolbarComponent } from './toolbar/toolbar.component';

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

Note that above we included the ToolbarComponent in the declarations property array.

Before we go any further, we need to go back and do a little refactoring. If you have been following along with each part in this series you will recall that we previously imported the MdToolbarModule in our AppModule. Well, we are now going to move the toolbar out of the client/src/app/app.component.html template and into our new client/src/app/shared/toolbar/toolbar.component.html template. Before we do that, we should remove the import of the MdToolbarModule.

Remove the import statement on the top of the client/src/app/app.module.ts file, and then remove the MdToolbarModule from the imports array in the class decorator.

Here is what the AppModule decorator should look like now:

@NgModule({
  declarations: [AppComponent],
  imports: [BrowserAnimationsModule, BrowserModule, SharedModule],
  providers: [],
  bootstrap: [AppComponent],
})
export class AppModule {}

Next, remove the public title string property from the AppComponent class in client/src/app/app.component.ts. and move it into our new ToolbarComponent class:

import {
  ChangeDetectionStrategy,
  Component,
  EventEmitter,
  Output,
} from '@angular/core';

@Component({
  changeDetection: ChangeDetectionStrategy.OnPush,
  selector: 'app-toolbar',
  templateUrl: './toolbar.component.html',
  styleUrls: ['./toolbar.component.scss']
})
export class ToolbarComponent {

  @Output() public openSidenav = new EventEmitter<void>();

  public title = 'Tour of Heros';

}

There are few other change to note:

Let’s update the client/src/app/shared/toolbar/toolbar.component.html template:

<md-toolbar>
  <button md-icon-button (click)="openSidenav.emit()">
    <md-icon>menu</md-icon>
  </button>
  {{ title }}
</md-toolbar>

In the ToolbarComponent template:

If you are using the Angular Language Service in your editor, you may have noted some errors being reported in our updated HTML template indicating that md-toolbar is an unknown element:

md-toolbar is an unknown element

If you are unfamiliar with the Angular Language Service, check out my post on installing the Angular Language Service. It is an incredible tool for an Angular developer, and works with several popular editors.

The problem is that we removed the MdToolbarModule module import from AppModule, but we did not add it to our SharedModule module. Let’s go ahead and add a few Angular Material modules that we need, namely the MdIconModule and the MdToolbarModule to the SharedModule module:

import { MdIconModule, MdToolbarModule } from '@angular/material';

@NgModule({
  imports: [CommonModule, MdIconModule, MdToolbarModule],
  declarations: [ToolbarComponent],
})
export class SharedModule {}

Then, export the ToolbarComponent out of the SharedModule module so that it is public, and can be used in other modules in our application:

@NgModule({
  imports: [CommonModule, MdIconModule, MdToolbarModule],
  declarations: [ToolbarComponent],
  exports: [ToolbarComponent],
})
export class SharedModule {}

To export the component we include the class in the array of exports in the class decorator for the SharedModule in client/src/app/shared/shared.module.ts.

The ToolbarComponent is now ready to be used by other modules in our application.

Actions

Let’s start diving into using reactive programming, specifically, using the ngrx library.

The first step is to define the actions for our shared module. Our shared module will have a single state, whether the sidenav is open or closed, which will be a boolean property. The actions on this state are pretty simple:

  1. open the sidenav
  2. close the sidenav.

Let’s create a new file at client/src/app/shared/shared.actions.ts:

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

export const SIDENAV_CLOSE = '[shared] Close Sidenav';
export const SIDENAV_OPEN = '[shared] Open Sidenav';

export class CloseSidenavAction implements Action {
  readonly type = SIDENAV_CLOSE;
}

export class OpenSidenavAction implements Action {
  readonly type = SIDENAV_OPEN;
}

export type Actions =
  CloseSidenavAction
  | OpenSidenavAction;

Let’s review:

Reducers

A reducer is a pure function that modifies the state of our application based on the action dispatched to the store. It is a pure function because we can expect the same value/behavior/output when provided the same input.

Create a new file: client/src/app/shared/shared.reducers.ts:

import { SIDENAV_CLOSE, SIDENAV_OPEN, Actions } from './shared.actions';

export interface State {
  showSidenav: boolean;
}

const initialState: State = {
  showSidenav: false,
};

export function reducer(state = initialState, action: Actions): State {
  switch (action.type) {
    case SIDENAV_CLOSE:
      return {
        ...state,
        ...{
          showSidenav: false,
        },
      };

    case SIDENAV_OPEN:
      return {
        ...state,
        ...{
          showSidenav: true,
        },
      };

    default:
      return state;
  }
}

export const getShowSidenav = (state: State) => state.showSidenav;

Let’s review the client/src/app/shared/shared.reducers.ts file:

Now that we have our shared module reducer function defined, let’s create a single module within our application that will combine all of the module states for our application.

To do this, create a new file: client/src/app/app.reducers.ts:

import { createSelector } from 'reselect';
import { ActionReducer } from '@ngrx/store';
import { RouterState, routerReducer } from '@ngrx/router-store';
import { environment } from '../environments/environment';
import { compose } from '@ngrx/core/compose';
import { combineReducers } from '@ngrx/store';
import { storeFreeze } from 'ngrx-store-freeze';

import * as shared from './shared/shared.reducers';

export interface State {
  router: RouterState;
  shared: shared.State;
}

const reducers = {
  router: routerReducer,
  shared: shared.reducer,
};

const developmentReducer: ActionReducer<State> = compose(
  storeFreeze,
  combineReducers
)(reducers);
const productionReducer: ActionReducer<State> = combineReducers(reducers);

export function reducer(state: any, action: any) {
  if (environment.production) {
    return productionReducer(state, action);
  } else {
    return developmentReducer(state, action);
  }
}

/**
 * Shared Reducers
 */
export const getSharedState = (state: State) => state.shared;
export const getShowSidenav = createSelector(
  getSharedState,
  shared.getShowSidenav
);

There is a bit of ceremony here, but let’s quickly review:

LayoutComponent

The next step is to create a new LayoutComponent. This new component will serve as a wrapper, if you will, for the content of our application. It will contain both the sidebar and the toolbar for the application, and we will use transclusion to embed our application’s content within the layout.

To get started, create the new component using the Angular CLI:

$ ng g c shared/layout

Our template is going to use two modules provided by Angular Material: the MdSidenavModule and the MdButtonModule. Further, our template is also going to use the FlexLayoutModule to easily implement a flex based layout. So, let’s import these into the SharedModule in client/src/app/shared/shared.module.ts:

import {
  MdButtonModule,
  MdIconModule,
  MdSidenavModule,
  MdToolbarModule
} from "@angular/material";
import { FlexLayoutModule } from "@angular/flex-layout";

@NgModule({
  imports: [
    CommonModule,
    FlexLayoutModule
    MdButtonModule,
    MdIconModule,
    MdSidenavModule,
    MdToolbarModule
  ],
  declarations: [
    ToolbarComponent
  ],
  exports: [
    ToolbarComponent
  ]
})
export class SharedModule { }

Next, let’s update the client/src/app/shared/layout.component.html template that was generated by the CLI:

<md-sidenav-container>
  <md-sidenav [opened]="open | async">
    <md-list>
      <md-list-item>
        <button md-button (click)="heros()">Heros</button>
      </md-list-item>
      <md-list-item>
        <button md-button (click)="add()">Add Hero</button>
      </md-list-item>
    </md-list>
  </md-sidenav>
  <app-toolbar (openSidenav)="openSidenav()"></app-toolbar>
  <div fxLayout="column" fxLayoutAlign="start stretch">
    <div fxLayout="row" fxLayoutAlign="center">
      <div fxFlex="90%" fxFlex.md="66.6667%" fxFlex.gt-md="50%">
        <ng-content></ng-content>
      </div>
    </div>
  </div>
</md-sidenav-container>

Let’s review our new layout template:

With the template created, let’s update the LayoutComponent class:

import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { Observable } from "rxjs/Observable";
import { Store } from "@ngrx/store";
import { go } from "@ngrx/router-store";
import { State, getShowSidenav } from "../../app.reducers";
import { OpenSidenavAction } from "../../shared/shared.actions";

@Component({
  changeDetection: ChangeDetectionStrategy.OnPush,
  selector: 'app-layout',
  templateUrl: './layout.component.html',
  styleUrls: ['./layout.component.scss']
})
export class LayoutComponent implements OnInit {

  public open: Observable<boolean>;

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

  ngOnInit() {
    this.open = this.store.select(getShowSidenav);
  }

  public add() {
    // placeholder
  }

  public heros() {
    this.store.dispatch(go(["/heros"]));
  }

  public openSidenav() {
    this.store.dispatch(new OpenSidenavAction());
  }

}

As always, let’s review:

Configure Store

We need to configure the store for our application. To do this, we need to update the AppModule:

import { StoreModule } from '@ngrx/store';
import { reducer } from './app.reducers';

@NgModule({
  declarations: [AppComponent],
  imports: [
    BrowserAnimationsModule,
    BrowserModule,
    SharedModule,
    StoreModule.provideStore(reducer),
  ],
  providers: [],
  bootstrap: [AppComponent],
})
export class AppModule {}

Note that we invoke the static provideStore() method on the StoreModule, providing our application’s combined reducer function from the client/src/app/app.reducers.ts module.

Hero model

Our application is coming along nicely, and we have started using RxJS and ngrx. But, all we have is a sidenav and a button at this point.

So, what’s next? We need to build a model of our data.

If you recall from the first part of this series we built a REST API using Express and Mongoose. We defined a collection in MongoDb named “Heros”, which has a single string property: name.

So, similar to the model we defined for Mongoose, we will also define a model for our Angular application. I do see this as a bit of duplication, which we generally want to avoid, but this is the price we pay for having a client model and a server model.

Create a new client/src/app/models directory:

$ mkdir models

Then, create a new file: client/src/app/models/hero.ts:

export class Hero {
  _id?: string;
  name?: string;
}

Pretty simple, eh? The _id property is the MongoDb id for the document. And, the name property we defined to store the string name of the hero.

HerosService

With our model defined, we now need to create a service in our Angular application that will use the Angular Http service to create(), delete(), get(), list() and update() our heros.

To get started, use the Angular CLI to generate a new service:

$ ng g s core/services/heros.service

Now, let’s modify the service at client/src/app/core/services/heros.service.ts:

import { Injectable } from '@angular/core';
import { Http, Response } from "@angular/http";

// rxjs
import { Observable } from "rxjs/Observable";

// models
import { Hero } from "../../models/hero";

@Injectable()
export class HerosService {

  private readonly URL = "http://localhost:8080/api/heros"

  constructor(
    protected http: Http,
  ) {}

  public create(hero: Hero): Observable<Hero> {
    return this.http
      .post(this.URL, hero)
      .map(this.extractObject);
  }

  public delete(hero: Hero): Observable<Hero> {
    return this.http
      .delete(`${this.URL}/${hero._id}`)
      .map(result => hero);
  }

  public get(id: string): Observable<Hero> {
    return this.http
      .get(`${this.URL}/${id}`)
      .map(this.extractObject);
  }

  public list(): Observable<Array<Hero>> {
    return this.http
      .get(this.URL)
      .map(response => response.json() || []);
  }

  public update(hero: Hero): Observable<Hero> {
    return this.http
      .put(`${this.URL}/${hero._id}`, hero)
      .map(this.extractObject);
  }

  private extractObject(res: Response): Object {
    const data: any = res.json();
    return data || {};
  }

}

Let’s review our new HerosService:

The next thing we need to do for the service to work is to specify the class in our AppModule in the providers array. We also need to add the HttpModule to the imports array in AppModule:

import { HttpModule } from '@angular/http';

@NgModule({
  declarations: [AppComponent],
  imports: [
    BrowserAnimationsModule,
    BrowserModule,
    HttpModule,
    SharedModule,
    StoreModule.provideStore(reducer),
  ],
  providers: [HerosService],
  bootstrap: [AppComponent],
})
export class AppModule {}

The HerosService is now ready to be injected into our components as needed.

HerosModule

Create a new module in the application using the Angular CLI:

$ ng g m heros

Don’t forget to go back into the AppModule and to import this new module:

import { HerosModule } from './heros/heros.module';

@NgModule({
  declarations: [AppComponent],
  imports: [
    BrowserAnimationsModule,
    BrowserModule,
    HerosModule,
    HttpModule,
    SharedModule,
    StoreModule.provideStore(reducer),
  ],
  providers: [HerosService],
  bootstrap: [AppComponent],
})
export class AppModule {}

Note that we included the HerosModule in the array of imports.

Now, let’s update the generated client/src/app/heros/heros.module.ts file:

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { MdDialogModule, MdSnackBarModule } from '@angular/material';
import { SharedModule } from '../shared/shared.module';

@NgModule({
  imports: [CommonModule, MdDialogModule, MdSnackBarModule, SharedModule],
  declarations: [],
})
export class HerosModule {}

Here’s what we did:

Heros Actions

Similar to the actions we defined for our shared module, we will also create actions for the heros module.

Create a new file: client/src/app/heros/heros.actions.ts:

import { Action } from '@ngrx/store';
import { Hero } from "../models/hero";

export const LOAD_HEROS = '[heros] Load heros';
export const LOAD_HEROS_ERROR = '[heros] Load heros error';
export const LOAD_HEROS_SUCCESS = '[heros] Load heros success';

export class LoadHerosAction implements Action {
  readonly type = LOAD_HEROS;
}

export class LoadHerosErrorAction implements Action {
  readonly type = LOAD_HEROS_ERROR;
  constructor(public payload: { error: Error }) {}
}

export class LoadHerosSuccessAction implements Action {
  readonly type = LOAD_HEROS_SUCCESS;
  constructor(public payload: { heros: Hero[] }) {}
}

export type Actions =
  LoadHerosAction
  | LoadHerosErrorAction
  | LoadHerosSuccessAction;

We have defined some initial actions to load the heros, along with actions for when there is an exception and when the action is successful.

Note that we have defined the constructor() functions for the LoadHerosErrorAction and LoadHerosSuccessAction actions. In each constructor() function we defined a public parameter property named payload, which is required. The payload will either contain the Error that was thrown, or the array of Hero objects that were returned from the REST API.

Heros Reducer

Next, we need to define the reducer function for our actions in the heros module.

Create a new file: client/src/app/heros/heros.reducers.ts:

import {
  LOAD_HEROS,
  LOAD_HEROS_ERROR,
  LOAD_HEROS_SUCCESS,
  Actions,
} from './heros.actions';
import { Hero } from '../models/hero';

export interface State {
  error?: Error;
  heros: Hero[];
}

const initialState: State = {
  heros: [],
};

export function reducer(state = initialState, action: Actions): State {
  switch (action.type) {
    case LOAD_HEROS:
      return {
        ...state,
        ...{
          error: undefined,
          heros: [],
        },
      };

    case LOAD_HEROS_ERROR:
      return {
        ...state,
        ...{
          error: action.payload.error,
        },
      };

    case LOAD_HEROS_SUCCESS:
      return {
        ...state,
        ...{
          heros: action.payload.heros,
        },
      };

    default:
      return state;
  }
}

export const getHeros = (state: State) => state.heros;

There are a few things to note:

Now, we need to add the heros reducer to our client/src/app/app.reducers.ts module:

import * as heros from './heros/heros.reducers';

export interface State {
  heros: heros.State;
  router: RouterState;
  shared: shared.State;
}

const reducers = {
  heros: heros.reducer,
  router: routerReducer,
  shared: shared.reducer,
};

/**
 * Heros Reducers
 */
export const getHerosState = (state: State) => state.heros;
export const getHeros = createSelector(getHerosState, heros.getHeros);

Note that I omitted some code above for simplicity. To add our heros reducers and state we:

  1. Import all exported members from the heros.reducers.ts module.
  2. Add a new heros property to the State interface with a reference to the State of our heros module.
  3. Add a new heros property to the reducers object with a reference to the reducer() function defined in the heros.reducers.ts module.

Heros Effects

While we previously created actions and a reducer for our shared module to control the state of the sidenav, we didn’t have a need to implement any side effects. A side effect can be simply defined as something that we want to perform as a result of an action.

In this case we will invoke our REST API using the HerosService for populating the array of heros in our state.

Create a new file: client/src/app/heros/heros.effects.ts:

import { Injectable } from "@angular/core";
import { MdDialog, MdSnackBar } from "@angular/material";
import { Effect, Actions, toPayload } from "@ngrx/effects";
import { Action } from "@ngrx/store";
import { Observable } from "rxjs/Observable";
import { empty } from "rxjs/observable/empty";
import "rxjs/add/operator/catch";
import "rxjs/add/observable/of";
import "rxjs/add/operator/switchMap";
import { HerosService } from "../core/services/heros.service";
import {
  LOAD_HEROS,
  LOAD_HEROS_ERROR,
  LoadHerosErrorAction,
  LoadHerosSuccessAction
} from "./heros.actions";

@Injectable()
export class HeroEffects {

  @Effect()
  public loadHeros: Observable<Action> = this.actions
    .ofType(LOAD_HEROS)
    .map(toPayload)
    .switchMap(payload => {
      return this.herosService.list()
        .map(heros => new LoadHerosSuccessAction({ heros: heros }))
        .catch(error => Observable.of(new LoadHerosErrorAction({ error: error })));
    });

  @Effect()
  public loadHerosError: Observable<Action> = this.actions
    .ofType(LOAD_HEROS_ERROR)
    .map(toPayload)
    .switchMap(payload => {
      this.mdSnackbar.open("Oops. Something went wrong.", null, {
        duration: 1000
      });
      return empty();
    });

  constructor(
    private actions: Actions,
    private herosService: HerosService,
    private mdDialog: MdDialog,
    private mdSnackbar: MdSnackBar
  ) { }

}

Some things to note:

Ok, now we need to import the EffectsModule into the AppModule, and then run() each effect class. In this instance, we are going to run() our injectable HeroEffects class. As you build out your applications you will likely have multiple effects, for each child module of your application. You will need to import each effect class and run() each effect class in the AppModule.

import { EffectsModule } from '@ngrx/effects';

@NgModule({
  declarations: [AppComponent],
  imports: [
    BrowserAnimationsModule,
    BrowserModule,
    EffectsModule.run(HeroEffects),
    HerosModule,
    HttpModule,
    SharedModule,
    StoreModule.provideStore(reducer),
  ],
  providers: [HerosService],
  bootstrap: [AppComponent],
})
export class AppModule {}

Note that we imported the EffectsModule. Then, in the imports array for our AppModule decorator we invoke the run() static method, providing our injectable HeroEffects class.

Our hero effects are now wired up.

IndexComponent

Before we get to configuring the routing and navigation for our application we need to create a component that will serve as the /heros index component. As appropriate, we’ll name this IndexComponent.

Using the CLI, generate the IndexComponent in the heros module:

$ ng g c heros/index

Let’s update the generated client/src/app/heros/heros.component.html template:

<app-layout>
  <h1>Heros</h1>
</app-layout>

We wrap our content using the <app-layout> component. If you recall we talked briefly about the idea of transclusion using the <ng-content> component. Now, we are seeing this in use. Within the <app-layout> element we have included a heading placeholder with the text “Heros”. This header and text will be placed within the <app-layout> template where we defined the <ng-content> component.

We will later update this template to use a component to list the heros.

Now, let’s update the client/src/app/heros.component.ts component:

import { Component, OnInit } from '@angular/core';
import { Observable } from "rxjs/Observable";
import { Store } from "@ngrx/store";
import { State, getHeros } from "../../app.reducers";
import { Hero } from "../../models/hero";
import { LoadHerosAction } from "../heros.actions";

@Component({
  selector: 'app-index',
  templateUrl: './index.component.html',
  styleUrls: ['./index.component.scss']
})
export class IndexComponent implements OnInit {

  public heros: Observable<Array<Hero>>;

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

  ngOnInit() {
    this.heros = this.store.select(getHeros);
    this.store.dispatch(new LoadHerosAction());
  }

}

Our template is going to eventually need to obtain the list of heros, so we’ve gone ahead and implemented the code that will be required.

Routing

Almost every Angular application will require some sort of routing for the user to navigate the application. As the documentation states:

The Angular Router enables navigation from one view to the next as users perform application tasks.

To get started, create a new file: client/src/app/app-routing.module.ts:

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

const routes: Routes = [
  {
    path: 'heros',
    loadChildren: './heros/heros.module#HerosModule',
  },
  {
    path: '',
    pathMatch: 'full',
    redirectTo: '/heros',
  },
  {
    path: '**',
    redirectTo: '/404',
  },
];

@NgModule({
  exports: [RouterModule],
  imports: [RouterModule.forRoot(routes)],
})
export class AppRoutingModule {}

Let’s review the AppRoutingModule:

We first define the routes for our application, and then we use the RouterModule.forRoot() method to establish the root routes for our application. Note that we are also lazy loading the HerosModule using the loadChildren property for the path heros.

When you first load the application and do not specify a path we will redirect to “/heros”. If the route path specified does not match any of the specified routes, we will redirect the user to a 404 page.

Now, create a new file client/src/app/heros/heros-routing.module.ts:

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { IndexComponent } from './index/index.component';

const routes: Routes = [
  {
    path: '',
    component: IndexComponent,
  },
  {
    path: '**',
    redirectTo: '/404',
  },
];

@NgModule({
  exports: [RouterModule],
  imports: [RouterModule.forChild(routes)],
})
export class HerosRoutingModule {}

The heros module routing defines a single route for the IndexComponent. Again, we redirect to the 404 page when the route specified by the user is not found.

Note that we invoke the forChild() method on the RouterModule class in this instance. This is because we are within a child module (/heros) and not at the root level, as required by the Angular router.

Next, we need to import the AppRoutingModule into our AppModule in client/src/app/app.module.ts. We also need to import the RouterStoreModule module from @ngrx/router-store:

import { AppRoutingModule } from './app-routing.module';
import { RouterStoreModule } from '@ngrx/router-store';

@NgModule({
  declarations: [AppComponent],
  imports: [
    AppRoutingModule,
    BrowserAnimationsModule,
    BrowserModule,
    EffectsModule.run(HeroEffects),
    HttpModule,
    HerosModule,
    RouterStoreModule.connectRouter(),
    SharedModule,
    StoreModule.provideStore(reducer),
  ],
  providers: [HerosService],
  bootstrap: [AppComponent],
})
export class AppModule {}

We add the AppRoutingModule to the array of imports, and we also add the RouterStoreModule to the array of imports, invoking the connectRouter() method.

The final step for our routing is to go back into the client/src/app/app.component.html template and to remove the existing contents and to include a single <router-outlet> element:

<router-outlet></router-element>

When the router matches the URL to the /heros route and displays the IndexComponent, the contents of the template will be placed after the <router-outlet> element we have defined.

Demo

A couple of things should happen if we serve our application and test it:

Go ahead and use the CLI to serve the application:

$ ng serve

Here is a quick demo of our application so far:

Demo showing basic app functionality

Close Sidenav

But, you might notice an issue. While our sidenav opens, and we can close it by clicking on the container, we cannot open it again.

Demo showing sidenav opening

The issue is that we never toggle the boolean value to false, so the opened input binding value on the <md-sidenav> element is invalid.

We can fix this by using the backdropClick output binding on the <md-sidenav-container> in the client/src/app/shared/layout/layout.component.html template:

<md-sidenav-container
  fullscreen
  (backdropClick)="closeSidenav()"
></md-sidenav-container>

When the backdropClick output event is emitted we invoke a new method named closeSidenav in the LayoutComponent:

import { CloseSidenavAction, OpenSidenavAction } from "../../shared/shared.actions";

export class LayoutComponent {

  public closeSidenav() {
    this.store.dispatch(new CloseSidenavAction());
  }

}

The closeSidenav() method simply dispatches the CloseSidenavAction action, which we imported as well. When the action is dispatched our reducer function will toggle the showSidenav boolean property in the shared module State to false.

Create Actions

While our application will now list the heros in our MongoDb collection, we do not have any heros yet. And, that’s a pity.

So, I think the next logical step is to add the functionality to create a new hero. To do that, the first step is to create the necessary actions:

Let’s add these new actions to the client/src/app/heros/heros.actions.ts module:

import { Action } from '@ngrx/store';
import { Hero } from "../models/hero";

export const CREATE_HERO = '[heros] Create hero';
export const CREATE_HERO_ERROR = '[heros] Create hero error';
export const CREATE_HERO_SUCCESS = '[heros] Create hero success';
export const CREATE_HERO_DIALOG_CLOSE = '[heros] Close create hero dialog';
export const CREATE_HERO_DIALOG_OPEN = '[heros] Open create hero dialog';
export const LOAD_HEROS = '[heros] Load heros';
export const LOAD_HEROS_ERROR = '[heros] Load heros error';
export const LOAD_HEROS_SUCCESS = '[heros] Load heros success';

export class CreateHeroAction implements Action {
  readonly type = CREATE_HERO;
  constructor(public payload: { hero: Hero }) {}
}

export class CreateHeroErrorAction implements Action {
  readonly type = CREATE_HERO_ERROR;
  constructor(public payload: { error: Error }) {}
}

export class CreateHeroSuccessAction implements Action {
  readonly type = CREATE_HERO_SUCCESS;
  constructor(public payload: { hero: Hero }) {}
}

export class CreateHeroDialogCloseAction implements Action {
  readonly type = CREATE_HERO_DIALOG_CLOSE;
}

export class CreateHeroDialogOpenAction implements Action {
  readonly type = CREATE_HERO_DIALOG_OPEN;
}

export class LoadHerosAction implements Action {
  readonly type = LOAD_HEROS;
}

export class LoadHerosErrorAction implements Action {
  readonly type = LOAD_HEROS_ERROR;
  constructor(public payload: { error: Error }) {}
}

export class LoadHerosSuccessAction implements Action {
  readonly type = LOAD_HEROS_SUCCESS;
  constructor(public payload: { heros: Hero[] }) {}
}

export type Actions =
  CreateHeroAction
  | CreateHeroErrorAction
  | CreateHeroSuccessAction
  | CreateHeroDialogCloseAction
  | CreateHeroDialogOpenAction
  | LoadHerosAction
  | LoadHerosErrorAction
  | LoadHerosSuccessAction;

There are a couple of things to note:

Create Reducer

With our actions defined, our next step is to go to the client/src/app/heros/heros.reducers.ts module and to update the state for each action as necessary:

import {
  CREATE_HERO,
  CREATE_HERO_ERROR,
  CREATE_HERO_SUCCESS,
  LOAD_HEROS,
  LOAD_HEROS_ERROR,
  LOAD_HEROS_SUCCESS,
  Actions,
} from './heros.actions';
import { Hero } from '../models/hero';

export interface State {
  error?: Error;
  hero?: Hero;
  heros: Hero[];
}

const initialState: State = {
  heros: [],
};

export function reducer(state = initialState, action: Actions): State {
  switch (action.type) {
    case CREATE_HERO:
      return {
        ...state,
        ...{
          error: undefined,
          hero: undefined,
        },
      };

    case CREATE_HERO_ERROR:
      return {
        ...state,
        ...{
          error: action.payload.error,
        },
      };

    case CREATE_HERO_SUCCESS:
      return {
        ...state,
        ...{
          hero: action.payload.hero,
          heros: [...state.heros, action.payload.hero],
        },
      };

    case LOAD_HEROS:
      return {
        ...state,
        ...{
          error: undefined,
          heros: [],
        },
      };

    case LOAD_HEROS_ERROR:
      return {
        ...state,
        ...{
          error: action.payload.error,
        },
      };

    case LOAD_HEROS_SUCCESS:
      return {
        ...state,
        ...{
          heros: action.payload.heros,
        },
      };

    default:
      return state;
  }
}

export const getHero = (state: State) => state.hero;
export const getHeros = (state: State) => state.heros;

We have made several changes:

Create Dialog

Before we start coding up our side effects, we need to create a new HeroCreateDialogComponent:

$ ng g c shared/hero-create-dialog

First, let’s update the client/src/app/shared/hero-create-dialog/hero-create-dialog.component.html template:

<div class="header">
  <button md-dialog-close md-icon-button>
    <md-icon>close</md-icon>
  </button>
  <h1 md-dialog-title>Create Hero</h1>
</div>
<md-dialog-content>
  <form [formGroup]="form">
    <md-input-container>
      <input mdInput formControlName="name" placeholder="Name" />
    </md-input-container>
  </form>
</md-dialog-content>
<md-dialog-actions>
  <button md-button (click)="submit()" color="primary">Create</button>
</md-dialog-actions>

If you are unfamiliar with Angular Material’s dialog component, I recommend you head over and read through the documentation.

In our dialog template:

We are using Angular’s reactive forms module in this component. Therefore, we need to import the ReactiveFormsModule, as well as the FormsModule, into our SharedModule in client/src/app/shared/shared.module.ts file.

We also need to add the HeroCreateDialogComponent to a new property named entryComponents in our module decorator:

import { ReactiveFormsModule, FormsModule } from '@angular/forms';

@NgModule({
  entryComponents: [HeroCreateDialogComponent],
  imports: [
    CommonModule,
    FlexLayoutModule,
    FormsModule,
    MdDialogModule,
    MdButtonModule,
    MdIconModule,
    MdInputModule,
    MdListModule,
    MdSidenavModule,
    MdToolbarModule,
    ReactiveFormsModule,
  ],
  declarations: [ToolbarComponent, HeroCreateDialogComponent, LayoutComponent],
  exports: [ToolbarComponent, LayoutComponent],
})
export class SharedModule {}

Note that I omitted all of the imports at the top of the file, and only included the import of the ReactiveFormsModule and FormsModule.

Now, let’s move on to updating the HeroCreateDialogComponent as necessary:

import {
  ChangeDetectionStrategy,
  Component,
  OnInit
} from '@angular/core';
import { FormBuilder, FormGroup, Validators } from "@angular/forms";
import { MdDialogRef } from "@angular/material";
import { Store } from "@ngrx/store";
import { State } from "../../app.reducers";
import { CreateHeroAction } from "../../heros/heros.actions";
import { Hero } from "../../models/hero";

@Component({
  changeDetection: ChangeDetectionStrategy.OnPush,
  templateUrl: './hero-create-dialog.component.html',
  styleUrls: ['./hero-create-dialog.component.scss']
})
export class HeroCreateDialogComponent implements OnInit {

  public form: FormGroup;

  constructor(
    private formBuilder: FormBuilder,
    private mdDialogRef: MdDialogRef<HeroCreateDialogComponent>,
    private store: Store<State>
  ) { }

  ngOnInit() {
    this.form = this.formBuilder.group({
      name: ["", Validators.required]
    });
  }

  public submit() {
    const hero: Hero = this.form.value;
    this.store.dispatch(new CreateHeroAction({ hero: hero }));
    this.mdDialogRef.close();
  }

}

As always, let’s review:

Let’s not forget to wire up our dialog to the “Add Hero” button in our sidebar. If you recall, we created a placeholder add() method in the LayoutComponent class. Let’s implement this now.

Import the CreateHeroDialogOpenAction action and then update the add() method in the LayoutComponent class in client/src/app/shared/layout/layout.component.ts:

import { CreateHeroDialogOpenAction } from "../../heros/heros.actions";

export class LayoutComponent implements OnInit {

  public add() {
    this.store.dispatch(new CreateHeroDialogOpenAction());
  }

}

The add() method uses the store to dispatch() the CreateHeroDialogOpenAction action.

Let’s also add some style to the HeroCreateDialogComponent in the client/src/app/shared/hero-create-dialog/hero-create-dialog.component.scss file:

:host {
  .header {
    position: relative;

    button[md-dialog-close] {
      position: absolute;
      top: -16px;
      right: -16px;
    }
  }

  md-dialog-content {
    md-input-container {
      min-width: 300px;
    }
  }
}

Create Hero Effects

While we defined the CreateHeroDialogOpenAction action previously, we do not have a side effect for this. So, nothing will happen when we click on the “Add Hero” button yet.

Let’s update the HeroEffects class to include additional side effects for creating a hero in the client/src/app/heros/heros.effects.ts file:

import {
  CREATE_HERO,
  CREATE_HERO_ERROR,
  CREATE_HERO_DIALOG_OPEN,
  LOAD_HEROS,
  LOAD_HEROS_ERROR,
  CreateHeroErrorAction,
  CreateHeroSuccessAction,
  LoadHerosErrorAction,
  LoadHerosSuccessAction
} from "./heros.actions";
import { HeroCreateDialogComponent } from "../shared/hero-create-dialog/hero-create-dialog.component";

export class HeroEffects {

  @Effect()
  public createHero: Observable<Action> = this.actions
    .ofType(CREATE_HERO)
    .map(toPayload)
    .switchMap(payload => {
      return this.herosService.create(payload.hero)
      .map(hero => new CreateHeroSuccessAction({ hero: hero }))
      .catch(error => Observable.of(new CreateHeroErrorAction({ error: error })));
    });

  @Effect()
  public createHeroError: Observable<Action> = this.actions
    .ofType(CREATE_HERO_ERROR)
    .map(toPayload)
    .switchMap(payload => {
      this.mdSnackbar.open("Oops. Something went wrong.", null, {
        duration: 1000
      });
      return empty();
    });

  @Effect()
  public createHeroDialogOpen: Observable<Action> = this.actions
    .ofType(CREATE_HERO_DIALOG_OPEN)
    .map(toPayload)
    .switchMap(payload => {
      this.mdDialog.open(HeroCreateDialogComponent);
      return empty();
    });
}

Note, I have not included all of the effects that we previously defined, just the additional effects for the following actions: CREATE_HERO, CREATE_HERO_ERROR and CREATE_HERO_DIALOG_OPEN.

A couple of other things to note:

List Heros

While we can now add heros (hurray!), we still cannot see the list of our heros. To do that, we’ll create a new HerosListComponent component:

$ng g c shared/heros-list

Let’s start by modifying the generated client/src/app/shared/heros-list/heros-list.component.html template:

<h1>Heros</h1>
<md-list *ngIf="heros && heros.length > 0">
  <md-list-item *ngFor="let hero of heros"> {{ hero.name }} </md-list-item>
</md-list>
<p \*ngIf="heros && heros.length === 0"><em>There are no heros. :(</em></p>

Here is a quick rundown of the template:

Next, let’s implement the HerosListComponent in client/src/app/shared/heros-list/heros-list.component.ts:

import {
  ChangeDetectionStrategy,
  Component,
  Input
} from '@angular/core';
import { Hero } from "../../models/hero";

@Component({
  changeDetection: ChangeDetectionStrategy.OnPush,
  selector: 'app-heros-list',
  templateUrl: './heros-list.component.html',
  styleUrls: ['./heros-list.component.scss']
})
export class HerosListComponent {

  @Input() public heros: Hero[] = [];

}

This component might be the simplest yet:

Now, let’s go back to our IndexComponent template and include the list of heros in client/src/app/heros/index/index.component.html:

<app-layout>
  <app-heros-list [heros]="heros | async"></app-heros-list>
</app-layout>

Note that we use the async pipe to subscribe to the Observable array of heros.

Create Demo

Go ahead and serve the application using the CLI:

$ ng serve

Here’s a demo of adding a hero to our Tour of Heros application:

Demo showing basic app functionality

Pretty cool!

Remove Actions

Ok, we can now list the heros and create additional heros. Let’s now work on adding the functionality to delete a hero.

To get started, let’s declare the actions. This should start to be familiar.

We’ll define three actions:

  1. Remove a hero.
  2. Removing a hero resulting in an exception
  3. Removing a hero was successful.

Open the client/src/app/heros/heros.actions.ts file and add the following actions:

export const REMOVE_HERO = '[heros] Remove hero';
export const REMOVE_HERO_ERROR = '[heros] Remove hero error';
export const REMOVE_HERO_SUCCESS = '[heros] Remove hero success';

export class RemoveHeroAction implements Action {
  readonly type = REMOVE_HERO;
  constructor(public payload: { hero: Hero }) {}
}

export class RemoveHeroErrorAction implements Action {
  readonly type = REMOVE_HERO_ERROR;
  constructor(public payload: { error: Error }) {}
}

export class RemoveHeroSuccessAction implements Action {
  readonly type = REMOVE_HERO_SUCCESS;
  constructor(public payload: { hero: Hero }) {}
}

export type Actions =
  CreateHeroAction
  | CreateHeroErrorAction
  | CreateHeroSuccessAction
  | CreateHeroDialogCloseAction
  | CreateHeroDialogOpenAction
  | LoadHerosAction
  | LoadHerosErrorAction
  | LoadHerosSuccessAction
  | RemoveHeroAction
  | RemoveHeroErrorAction
  | RemoveHeroSuccessAction;

Note that I excluded the existing actions we declared for loading and creating heros.

Here is what we did:

Remove Reducers

With our actions defined, let’s go to the client/src/app/heros/heros.reducers.ts file and add additional case statements for each action:

import {
  CREATE_HERO,
  CREATE_HERO_ERROR,
  CREATE_HERO_SUCCESS,
  CREATE_HERO_DIALOG_CLOSE,
  CREATE_HERO_DIALOG_OPEN,
  LOAD_HEROS,
  LOAD_HEROS_ERROR,
  LOAD_HEROS_SUCCESS,
  REMOVE_HERO,
  REMOVE_HERO_ERROR,
  REMOVE_HERO_SUCCESS,
  Actions,
} from './heros.actions';
import { Hero } from '../models/hero';

export interface State {
  error?: Error;
  hero?: Hero;
  heros: Hero[];
}

const initialState: State = {
  heros: [],
};

export function reducer(state = initialState, action: Actions): State {
  switch (action.type) {
    // code omitted

    case REMOVE_HERO:
      return {
        ...state,
        ...{
          error: undefined,
          hero: action.payload.hero,
        },
      };

    case REMOVE_HERO_ERROR:
      return {
        ...state,
        ...{
          error: action.payload.error,
        },
      };

    case REMOVE_HERO_SUCCESS:
      return {
        ...state,
        ...{
          heros: [...state.heros].filter(
            (hero) => hero._id !== action.payload.hero._id
          ),
        },
      };

    default:
      return state;
  }
}

export const getHeros = (state: State) => state.heros;

A few things to note:

Remove Effects

And now let’s update the HeroEffects class to include additional side effects for removing a hero. When a hero is removed we want to send a DELETE request to our REST API to remove the specified hero from the collection in MongoDb. To do that, we’ll invoke the delete() method on the HerosService:

export class HeroEffects {

  // code omitted

  @Effect()
  public removeHero: Observable<Action> = this.actions
    .ofType(REMOVE_HERO)
    .map(toPayload)
    .switchMap(payload => {
      return this.herosService.delete(payload.hero)
        .map(hero => new RemoveHeroSuccessAction({ hero: hero }))
        .catch(error => Observable.of(new RemoveHeroErrorAction({ error: error })));
    });

  @Effect()
  public removeHeroError: Observable<Action> = this.actions
    .ofType(REMOVE_HERO_ERROR)
    .map(toPayload)
    .switchMap(payload => {
      this.mdSnackbar.open("Oops. Something went wrong.", null, {
        duration: 1000
      });
      return empty();
    });

}

Note that I am only including the additional side effects, and I am not showing the importing of the necessary constant string values and action classes. Hopefully by this point you can easily figure out what you need to import.

Invoke Action

With our actions defined along with the updates to our reducer function and the additional side effects, we are now ready to implement the UI for removing a hero.

The first step is to update the client/src/app/shared/hero-list/hero-list.component.html template:

<h1>Heros</h1>
<md-list *ngIf="heros && heros.length > 0">
  <md-list-item *ngFor="let hero of heros">
    <button md-icon-button (click)="remove.emit(hero)">
      <md-icon>remove_circle</md-icon>
    </button>
    {{ hero.name }}
  </md-list-item>
</md-list>
<p \*ngIf="heros && heros.length === 0"><em>There are no heros. :(</em></p>

We have added a new <button> that has an event binding for the click event that will emit() the remove EventEmitter, specifying the hero to remove.

Let’s add some styling in client/src/app/shared/hero-list/hero-list.component.scss:

@import '~@angular/material/theming';

:host {
  md-list {
    md-list-item {
      width: 100%;
      position: relative;

      &:hover {
        background-color: mat-color($mat-grey, 100);
      }

      button {
        position: absolute;
        right: 0;
      }
    }
  }
}

We first import the theming sass file for Angular material, which contains the mat-color() mixin as well as the color variables, including $mat-grey. We have positioned the remove button so that it is on the right of each list item, and we highlight the list item when the cursor hovers over it.

The next step is to define the remove property in the HeroListComponent class in client/src/app/shared/hero-list/hero-list.component.ts:

export class HerosListComponent {

  @Output() public remove = new EventEmitter<Hero>();

}

Note that the EventEmitter generic type is a Hero object. We also imported the Output decorator from @angular/core.

Finally, we need to add output binding in the client/src/app/heros/index/index.component.html template for the remove event:

<app-layout>
  <app-heros-list
    [heros]="heros | async"
    (remove)="remove(\$event)"
  ></app-heros-list>
</app-layout>

In our IndexComponent we need to implement the remove method that will dispatch() the RemoveHeroAction action:

export class IndexComponent implements OnInit {

  public remove(hero: Hero) {
    this.store.dispatch(new RemoveHeroAction({ hero: hero }));
  }

}

Note that the $event parameter that was in the template is typed to a Hero object in the remove() method. If you recall, our EventEmitter had a generic type declared a Hero object, and in the HeroListComponent template we specify the current hero that we are going to remove within the ngFor iterator.

That’s it. We can now remove heros.

Remove Demo

Go ahead and serve your application using the CLI:

$ ng serve

Demo showing removing a hero

Redux DevTools

A great tool for developing using ngrx are the Redux devtools. To get started, install the @ngrx/store-devtools package using npm:

$ npm install @ngrx/store-devtools --save

Next, download and install the Redux Devtools Chrome extension.

Then, update the AppModule to import the StoreDevtoolsModule module:

import { StoreDevtoolsModule } from '@ngrx/store-devtools';

@NgModule({
  imports: [
    StoreModule.provideStore(rootReducer),
    // Note that you must instrument after importing StoreModule
    StoreDevtoolsModule.instrumentOnlyWithExtension({
      maxAge: 5,
    }),
  ],
})
export class AppModule {}

In the Chrome developer tools you will now have a new tab called “Redux”. You should see every action that is dispatched, and you can also inspect the current state of your application:

Redux Devtools

Download

If you haven’t already, you can download and run this entire application or fork a copy for your own use.

First, run the gulp tasks, then start the Node.js Express server.

$ gulp
$ chmod +x ./dist/bin/www
$ ./dist/bin/www

Then, serve the Angular client using the CLI:

$ ng serve