Brian Love
Angular + TypeScript Developer in Denver, CO

MEAN App: Reactive Programming

Reading time ~49 minutes

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:

  • The command we are executing is the ng command, for the Angular CLI.
  • The first parameter to the command is g, for generate.
  • The second paramter is m, for module
  • The third parameter is the name of the module that we are generating, shared.

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:

  • We are executing the ng command for the Angular CLI.
  • The first parameter, g, instructs the CLI to generate something.
  • The second parameter, c, instructs the CLI to generate a new component.
  • The fourth parameter instructs the CLI to generate a new component in our application’s root directory named toolbar within the client/src/app/shared directory.

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:

  • First, we are importing the ChangeDetectionStrategy enum, the EventEmitter class as well as the Output decorator.
  • We are specifying the changeDetection property within the component to OnPush. This will prevent Angular’s change detector from concerning itself with any changes that occur within this component, and all subsequent child components. Technically, what that means is that the change detection cycle is only executed once, during the component hydration phase. This is a good practice for stateless components. It also helps with the performance of our application.
  • Next, note that we have declared a public openSidenav property, which is an EventEmitter. When the user clicks on the menu button within the toolbar we will emit this event.
  • Finally, we have moved the public title property out of the AppComponent and into the ToolbarComponent.

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:

  • First, we are using Angular’s <md-toolbar> component to create a toolbar at the top of our application.
  • Within the toolbar we have a button with the md-icon-button directive. This will style our button appropriately for containing an icon. Further, we have an event binding for the click event, which will emit the openSidenav event that we declared as a public property of the ToolbarComponent.
  • We are also using Angular Material’s <md-icon> component to include the menu Material icon.
  • Finally, we use interpolation to output the public string title property value.

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:

  • First, we import the Action interface from the @ngrx/store module.
  • Second, we define two string constant values to represent the actions: SIDENAV_CLOSE and SIDENAV_OPEN. These must be unique values.
  • Third, we define two classes that implement the Action interface. Both classes contain a single readonly property named type, which is the constant value we defined previously.
  • Fourth, we export the Actions type using the union operator.

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:

  • First, we import the constant values SIDENAV_CLOSE and SIDENAV_OPEN, along with the Actions type from the shared.actions.ts file we previously created.
  • Second, we define our State interface. This will include all of the possible states for this module in our application.
  • Third, we define an initialState object that implements the State interface. By default the sidenav will be closed.
  • The reducer function will accept two parameters: the state of the application, and the action being performed. The default state is the initialState.
  • Within the reducer function we switch on the readonly type property value that we defined for our Actions.
  • Our reducer function will always return the State, defaulting to the current State.
  • Within the switch statement we have two cases, one for SIDENAV_CLOSE and another for SIDENAV_OPEN. In each case we return a new state, assigning a new value to the showSidenav property.
  • Finally, we export a constant function named getShowSidenav(). This function will return the boolean value that indicates the state of our sidenav, whether it is open (true) or closed (false).

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:

  • First, we import all of the necessary classes and functions from the various modules within the reselect and ngrx libraries.
  • We then import everything from the shared/shared.reducers.ts module (file).
  • We will use this to create a single State interface for our entire application. This includes the router state, which will be using the @ngrx/router-store State, as well as our shared state.
  • Finally, we define the getShowSidenav selector function using the createSelector method.

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:

  • First, all of our content is wrapped using the <md-sidenav-container> element.
  • Next, <md-sidenav> is a direct child of the container element. We also have an input binding for opened, whose value is bound to the open property within our component, which we will define shortly. Also note that we are using the async pipe since the open property is an Observable.
  • Within the sidebare we are using the <md-list> element with a list of navigational buttons. Note that each native <button> element has a md-button directive, along with an event binding for the click event. We’ll implement the heros() and add() methods shortly.
  • After the sidebar we implement our <app-toolbar>, binding to the output openSidenav, which invokes the openSidenav() method.
  • I am using Flex Layout to specify a column, and then a row. This is enables us to have a simple and responsive layout of our content.
  • Note the use of the <ng-content> element for transclusion. It’s a fancy word for taking any content that is between the opening <app-layout> element and the closing </app-layout> element to be transcluded (or inserted) into this placeholder. If you haven’t seen this before, don’t worry, it will make more sense soon.

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:

  • First, we import the ChangeDetectionStrategy enum.
  • Next, we updated the changeDetection strategy for our component to OnPush. As with our ToolbarComponent, the LayoutComponent is a stateless component. So, we can speed things up by telling Angular to not be concerned with any changes in our component after the initial hydration phase.
  • If you look back at our template we specified an input binding for the <md-sidebar> element, setting the opened binding to the asynchronous open value. Here, we define the public property, after importing the Observable class. The property’s generic type is set to boolean. If the value is true, then our sidebar is open, and conversely, if the value is false, then the sidebar is not open, or closed.
  • We inject the ngrx Store into our component in the constructor() function. Note that the Store generic type is set to our application’s combined State, which we imported from the client/src/app/app.reducers.ts module.
  • We have defined the add() method as a placeholder for adding a new hero. We will implement this later.
  • We also have a heros() method defined that will navigate to the /heros route using the go() function, which we imported from the @ngrx/router-store module.
  • Finally, the openSidenav() method will use the store to dispatch() a new action, namely the OpenSidenavAction that we defined in the client/src/app/shared/shared.actions.ts file.

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:

  • First, we import the Injectable interface from Angular. We decorate the HerosService class using Injectable(). This enables the class to be injectable using Angular dependency injector (DI).
  • Next, we import the Http and Response classes.
  • Then, we import the Observable class from RxJS. Our methods will be returning Observable objects for async operations.
  • Then, we import our newly created Hero model.
  • In the constructor() function we inject the Http service using Angular’s DI.
  • We then implement methods for our REST API’s verbs: DELETE, GET, PUT and POST. I’ve called these delete(), get(), update() and create(), respectively. There is also a list() method that GETs all of the hero documents in the MongoDb collection.
  • In each method we invoke the appropriate verb, supplying the necessary URL and data.
  • Finally, I have an extractData() method which will safely extract any JSON data that is returned in the response object. If no data is returned, the method returns an empty object.

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:

  • First, import the MdDialogModule and MdSnackbarModule modules from @angular/material. We will be showing a dialog to create a new hero, and we will use the snackbar component to show the user a message when an error occurs.
  • Next, import the SharedModule.
  • Finally, we add these modules to the imports property for our HerosModule.

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:

  • First, we import the LOAD_HEROS, LOAD_HEROS_ERROR and LOAD_HEROS_SUCCESS constant values along with the Actions type from the client/src/app/heros/heros.actions.ts module.
  • Next, we import the Hero model.
  • Then, we define the State interface for the heros module. In this case our state will have two properties: error and heros. The error property is optional and is of type Error. The heros property is required and is an array of Hero[] objects.
  • Then, we define the initialState. The error property is undefined and our heros property is an empty array.
  • The reducer() function switches on the three actions: LOAD_HEROS, LOAD_HEROS_ERROR and LOAD_HEROS_SUCCESS.
  • In each case we update the state as appropriate using the public payload property that we defined in the constructor() function for each action.
  • Finally, we have a getHeros() constant function that will return the array of heros in the State. We will use this to select() the heros that have been asynchronously loaded from our REST API using the HerosService we just created.

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:

  • First, we import all of the necessary classes, interfaces and methods.
  • We import the @injectable() interface so the HeroEffects class can be injected into the EffectsModule.
  • We import the MdDialog and MdSnackbar classes from @angular/material. This is so we can open a dialog and show a snackbar when something goes wrong.
  • We import the Effect decorator and the Actions observable from the @ngrx/effects module, along with the toPayload() function. The Effect decorator is used to decorate the properties in our effects class that will be invoked when an action is dispatched. The Actions observable will be used to observe all actions that are dispatched to the store. We will use the ofType() method to filter out only the action that we want to implement an effect for. Finally, the toPayload() helper function will return just the payload of the currently dispatched action.
  • We import the Action interface from the @ngrx/store module as our effect properties will return an Observable of an Action. In most instances we will return a new action. For example, after loading our heros, if there is an exception then we will return a new instance of the LoadHerosErrorAction. If the heros are loaded successfully, then we will return a new instance of the LoadHerosSuccessAction with the payload of our array of Hero[] objects specified.
  • We import the empty() observable. In the case of the error, our effect will return an observable that is empty.
  • We also import the HerosService so that we can interface with our REST API using the injectable service.
  • We have two public properties in our HeroEffects class: loadHeros and loadHerosError. Note that each property is a function that is decorated with the @Effect() decorator.
  • As a side effect, we want to invoke the HerosService.list() method when the LOAD_HEROS action is dispatched.
  • Further, as a side efect when an error occurs, we want to show a snackbar indicating that something went wrong. This is perhaps not the best error message, but at least we give some indication to our users that something went awry.
  • Finally, we inject the necessary class instances into the HeroEffects class in the constructor() function.

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.

  • First, we import the Observable class. The array of heros will be asynchronously retrieved from the HerosService.
  • Next, we import the Store.
  • Then, we import our application’s combined State, as well as the getHeros selector function we previously created in the client/src/app/app.reducers.ts file.
  • Then, we import our Hero model.
  • Finally, we import the LoadHerosAction, which we previously defined in client/src/app/heros/heros.actions.ts.
  • Within the IndexComponent class we have defined a public heros property. This is an Observable, whose generic type is defined as an Array of Hero objects.
  • We inject the Store into our component via the constructor() function.
  • Within the ngOnInit() lifecycle method we use our store to set the heros property. Note that we use the store’s select() method, providing the getHeros function as the selector.
  • Finally, we dispatch() the LoadHerosAction to our store.

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:

  • First, we import the NgModule decorator and the Routes and RouterModule. Routes is simply a type that defines an array of Route interfaces to ensure the proper definition of our routes array. And the RouterModule is used for defining either our application’s root routes or child routes.
  • The routes array contains objects for each route that we are declaring.
  • The path property is a string that specifies the route matching DSL.
  • The loadChildren property references the module to lazy load.
  • The pathMatch specifies the path matching strategy.
  • The redirectTo property is a URI path fragment that replaces the currently matched segment.

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:

  • First, when we load the application without specifying a URI we should be redirected to the /heros path.
  • Second, we should be able to see that the list() of heros is being loaded using our REST API.

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()">

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:

  • Open the create hero dialog.
  • Close the create hero dialog.
  • Create a hero.
  • A hero is successfully created.
  • There is an error creating a hero.

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:

  • First, we define several new constant string values matching the actions that I outlined previously.
  • Next, we define classes that implement the Action interface for each of the actions.
  • Each class has a readonly type property that is set to the appropriate constant string value.
  • As necessary, the class’s constructor() function is defined with the appropriate payload property object.
  • Finally, we add each class to the Actions type declaration.

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:

  • First, we imported the new actions that we need. We don’t need to modify the state when opening or closing the dialog, so we don’t need to import those two actions.
  • Next, we added a new hero property to the State interface. This will store the current hero that we are editing.
  • Next, we added a case-statement for each new action.
  • For the CREATE_HERO action we will reset the error and hero properties to undefined.
  • For the CREATE_HERO_ERROR action we will simply set the error property value to the error that is dispatched in the action.
  • For the CREATE_HERO_SUCCESS action we update the hero property to the newly created hero, and we update our heros array. Note that I am using the array deconstructor to append the hero onto a new array. This is because the heros object cannot be modified directly as it is frozen.

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 create a new div.header element, which we will use to provide some styling and positioning to the close button.
  • We add a native <button> element with the md-dialog-close directive. This will attach the necessary behavior to the button to close the dialog. We also use the md-icon-button directive to give our button the necessary styling to appropriately display the icon.
  • We add a h1 header using the md-dialog-title directive for displaying the title text.
  • We use the <md-dialog-content> directive to wrap the dialog content.
  • We define a <form> with the formGroup input binding to a public property within our component named form. This will be a new FormGroup that we will define shortly.
  • We are using the <md-input-container> directive from Angular Material to wrap the native <input> element, which will add style to our input using the Material design.
  • We have an input with the mdInput directive as required for Angular Materials input component. The <input> element also specifies the formControlName attribute with the value name, which will match up with our FormGroup definition. We also specify the placeholder attribute to provide a label for our input.
  • We use the <md-dialog-actions> directive to wrap the submit button.
  • We use a native <button> with an event binding for the click event to invoke the submit() method. We will define this in the HeroCreateDialogComponent next.

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:

  • First, we import the ChangeDetectionStrategy enum. As this component is stateless, we save valuable compute resources by instructing Angular’s change detector to only check for changes a single time, when the component is hydrated.
  • Next, we import the FormBuilder, FormGroup and Validators classes from the @angular/forms module. We will need these to wire up our reactive form.
  • Next, import the MdDialogRef class. This will enable us to have a reference to the current dialog, enabling us to close the dialog after the user submits the form.
  • Next, we import the Store, along with our application’s combined State.
  • Next, we import the CreateHeroAction that we defined.
  • Finally, we import the Hero model.
  • Within the HeroCreateDialogComponent we declare a single public form property, which is a FormGroup.
  • We inject the necessary class instances using our component’s constructor() function.
  • In the ngOnInit() lifecycle method we set the value of our form property using the group() method. We also specify the Validators.required method to ensure the name field value is not empty.
  • Finally, we have a submit() method which is invoked when the user clicks on the button in our template. We will first define a constant hero, which is the value of the form. Then, we dispatch() the CreateHeroAction action to our Store, supplying the necessary payload object, which is an object with the single hero property. Then we invoke the close() method to close the dialog.

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:

  • We import the constant CREATE_HERO, CREATE_HERO_ERROR and CREATE_HERO_DIALOG_OPEN string actions.
  • We also import the CreateHeroErrorAction and CreateHeroSuccessAction actions.
  • We added three new properties that are decorated with the @Effect() decorator: createHero, createHeroError and createHeroDialogOpen.
  • The createHero property will invoke the create() method on the HeroService instance to send the REST API request to create a new hero in MongoDb. We map() each result to a new CreateHeroSuccessAction action, supplying the necessary payload object with the required hero parameter, which is the newly created hero object that is returned from the REST API. We catch() any exceptions and return a new Observable.of() the CreateHeroErrorAction instance, again, supplying the necessary payload object with the required error property.
  • The createHeroError property will show a snackbar to the user indicating that an error occurred.
  • The createHeroDialogOpen property will open() our HeroCreateDialogComponent dialog.

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:

  • We are using the Angular Material list component, which we will only include in the generated output when we have at least one hero using the ngIf directive.
  • Using the ngFor directive we iterate over the array of heros. For each hero we output the hero’s name.
  • If there are no heros, we display a simple paragraph indicating as such.

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:

  • As this is another stateless component, we import the ChangeDetectionStrategy enum and set the changeDetection to OnPush. This should start to feel familiar, and is good practice.
  • We also import the Input class to decorate the heros property. The heros will be an input binding that will be provided in the IndexComponent template. If you recall, we already wired up our IndexComponent to retreive the list of heros.
  • We import the Hero model, as we can expect that the heros that are input into this component will be of type Hero.
  • Finally, we declare the heros component as an array of Hero objects. We set this to an empty array initially.

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:

  • First, we declare the REMOVE_HERO, REMOVE_HERO_ERROR and REMOVE_HERO_SUCCESS constant string values.
  • Second, we create three new action classes: RemoveHeroAction, RemoveHeroErrorAction and RemoveHeroSuccessAction.
  • Finally, we updated the Actions type with the additonal classes.

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:

  • We import the action constant string values and action classes.
  • We create a case statement that will update our state as necessary for each additional action.
  • Note that we use the array deconstructor syntax to update the array of heros since the object is frozen and cannot be modified directly. We also use the filter() method to remove the hero that was successfully removed.

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 = 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 = this.actions
    .ofType(REMOVE_HERO_ERROR)
    .map(toPayload)
    .switchMap(payload => {
      this.mdSnackbar.open("Oops. Something went wrong.", null, {
        duration: 1000
      });
      return empty();
    });

}</code></pre>

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 `

Brian Love

Hi, I'm Brian. I am interested in TypeScript, Angular and Node.js. I'm married to my best friend Bonnie, I live in Denver and I ski (a lot).