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

Brian Love

Angular Reactive (ngrx) Authentication

Learn how to add authentication to your Angular app using reactive (ngrx) extensions.

Goals

We are tasked with adding authentication to our Angular application, and we will be using the @ngrx/store for state management. Here are our goals:

Here is a screenshot of the completed application’s sign in page:

Sign in page

Source Code

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

Live Demo

The authenticate use the email address foo@test.com and the password password.

ngrx

ngrx is a set of sweet reactive extensions for your Angular application. There are 3 basic building blocks for using ngrx, insipired by Redux:

  1. Store - singly state management repository.
  2. Actions - emitted to change the state.
  3. Reducers - pure functions that that transform the state.

Further, reactive extensions enable us to work with Observable streams.

What is an Observable? Defined by RxJs:

Observables are lazy Push collections of multiple values.

Similar to a Promise object with the exception that a promise is only resolved/rejected once. An Observable is a stream of objects that can be subscribed to.

Getting Started

Here are a few principles you need to know before we get started:

OK, let’s start a new Angular project using the Angular CLI.

$ npm install -g @angular/cli
$ ng new angular-reactive-authentication

Next, install the following packages:

$ npm install @angular/flex-layout @angular/material @angular/animations @ngrx/core @ngrx/store @ngrx/effects @ngrx/router-store ngrx-store-freeze reselect --save

We’ll be using @ngrx/store for the state management of our Angular application. While the store provides a controlled state container, we will also use the @ngrx/router-store that binds @angular/router to our @ngrx/store. Additionally, we will isolate all of our side effects using @ngrx/effects.

Angular Material requires a little bit of setup to get things working. First, we’ll import a pre-built theme in the styles.css file. I am going to use the indigo-pink theme:

@import '~@angular/material/core/theming/prebuilt/indigo-pink.css';

Then, add the Material icons to the index.html file:

<link
  href="https://fonts.googleapis.com/icon?family=Material+Icons"
  rel="stylesheet"
/>

Structure

Let’s have a quick look of our application structure. Here is what my src directory looks like:

.
├── app
│   ├── app-routing.module.ts
│   ├── app.component.css
│   ├── app.component.html
│   ├── app.component.spec.ts
│   ├── app.component.ts
│   ├── app.module.ts
│   ├── app.reducers.ts
│   ├── core
│   │   ├── models
│   │   │   └── user.ts
│   │   ├── services
│   │   │   └── user.service.ts
│   │   └── util.ts
│   ├── not-found
│   │   ├── not-found.component.html
│   │   ├── not-found.component.scss
│   │   ├── not-found.component.spec.ts
│   │   └── not-found.component.ts
│   ├── shared
│   │   └── authenticated.guard.ts
│   └── users
│       ├── my-account
│       │   ├── my-account.component.html
│       │   ├── my-account.component.scss
│       │   ├── my-account.component.spec.ts
│       │   └── my-account.component.ts
│       ├── sign-in
│       │   ├── sign-in.component.html
│       │   ├── sign-in.component.scss
│       │   ├── sign-in.component.spec.ts
│       │   └── sign-in.component.ts
│       ├── sign-out
│       │   ├── sign-out.component.html
│       │   ├── sign-out.component.scss
│       │   ├── sign-out.component.spec.ts
│       │   └── sign-out.component.ts
│       ├── sign-up
│       │   ├── sign-up.component.html
│       │   ├── sign-up.component.scss
│       │   ├── sign-up.component.spec.ts
│       │   └── sign-up.component.ts
│       ├── users-routing.module.ts
│       ├── users.actions.ts
│       ├── users.effects.ts
│       ├── users.module.ts
│       └── users.reducers.ts
├── assets
├── environments
│   ├── environment.prod.ts
│   └── environment.ts
├── favicon.ico
├── index.html
├── main.ts
├── polyfills.ts
├── sass
│   └── _mixins.scss
├── styles.css
├── test.ts
├── tsconfig.app.json
├── tsconfig.spec.json
└── typings.d.ts

Models and Services

Before we dive into ngrx and our controllers, let’s get a basic understanding of the User model and UserService. The User model is defined in src/app/core/models/user.ts:

export class User {
  _id?: string;
  email?: string;
  firstName?: string;
  lastName?: string;
  password?: string;
}

Our User model is pretty slim. I included the _id string property as I will eventually build out the UserService to work with a REST API that uses MongoDb. As such, this is the object id assigned by MongoDb.

Using the Angular CLI we can get started with our UserService via:

$ ng g service core/services/user

Here is our UserService defined in src/app/core/services/user.ts:

import { Injectable } from "@angular/core";
import { Observable } from "rxjs/Observable";
import "rxjs/add/observable/throw";
import { User } from "../models/user";

export const MOCK_USER = new User();
MOCK_USER._id = "1";
MOCK_USER.email = "foo@test.com";
MOCK_USER.firstName = "Foo";
MOCK_USER.lastName = "Bar";
MOCK_USER.password = "password";

/**
 * The user service.
 */
@Injectable()
export class UserService {

  /**
   * True if authenticated
   * @type
   */
  private _authenticated = false;

  /**
   * Authenticate the user
   *
   * @param {string} email The user's email address
   * @param {string} password The user's password
   * @returns {Observable<User>} The authenticated user observable.
   */
  public authenticate(email: string, password: string): Observable<User> {
    // Normally you would do an HTTP request to determine to
    // attempt authenticating the user using the supplied credentials.

    if (email === MOCK_USER.email && password === MOCK_USER.password) {
      this._authenticated = true;
      return Observable.of(MOCK_USER);
    }

    return Observable.throw(new Error("Invalid email or password"));
  }

  /**
   * Determines if the user is authenticated
   * @returns {Observable<boolean>}
   */
  public authenticated(): Observable<boolean> {
    return Observable.of(this._authenticated);
  }

  /**
   * Returns the authenticated user
   * @returns {User}
   */
  public authenticatedUser(): Observable<User> {
    // Normally you would do an HTTP request to determine if
    // the user has an existing auth session on the server
    // but, let's just return the mock user for this example.
    return Observable.of(MOCK_USER);
  }

  /**
   * Create a new user
   * @returns {User}
   */
  public create(user: User): Observable<User> {
    // Normally you would do an HTTP request to POST the user
    // details and then return the new user object
    // but, let's just return the new user for this example.
    this._authenticated = true;
    return Observable.of(user);
  }

  /**
   * End session
   * @returns {Observable<boolean>}
   */
  public signout(): Observable<boolean> {
    // Normally you would do an HTTP request sign end the session
    // but, let's just return an observable of true.
    this._authenticated = false;
    return Observable.of(true);
  }
}

Let’s review the UserService class:

Routing

Let’s set up the routing in our application. To begin, create a new src/app/app-routing.module.ts file:

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

// components
import { NotFoundComponent } from './not-found/not-found.component';

const routes: Routes = [
  {
    path: 'users',
    loadChildren: './users/users.module#UsersModule',
  },
  {
    path: '',
    pathMatch: 'full',
    redirectTo: '/users/my-account',
  },
  {
    path: '404',
    component: NotFoundComponent,
  },
  {
    path: '**',
    redirectTo: '/404',
  },
];

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

Let’s review the AppRoutingModule class:

Next, let’s create the UsersRoutingModule class, which will specify the users module routing. You will need to create a new users directory in your app directory first.

Here is our src/app/users/users-routing.module.ts file:

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

// components
import { MyAccountComponent } from './my-account/my-account.component';
import { SignInComponent } from './sign-in/sign-in.component';
import { SignOutComponent } from './sign-out/sign-out.component';
import { SignUpComponent } from './sign-up/sign-up.component';

// routes
const routes: Routes = [
  {
    canActivate: [AuthenticationGuard],
    path: 'my-account',
    component: MyAccountComponent,
  },
  {
    path: 'sign-in',
    component: SignInComponent,
  },
  {
    path: 'sign-out',
    component: SignOutComponent,
  },
  {
    path: 'sign-up',
    component: SignUpComponent,
  },
  {
    path: '**',
    redirectTo: '/404',
  },
];

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

The UsersRoutingModule is very similar to the AppRoutingModule in that:

OK, we have stubbed out our models, services and routing.

Let’s quickly go back into the src/app/app.module.ts file to add our services and routing to the AppModule:

import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { NgModule } from '@angular/core';

// routing
import { AppRoutingModule } from './app-routing.module';

// components
import { AppComponent } from './app.component';
import { NotFoundComponent } from './not-found/not-found.component';

// services
import { UserService } from './core/services/user.service';

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

Next, we need to create a new UsersModule. To do that, let’s use the the Angular CLI:

$ ng g module users/users

Here is the UsersModule class in src/app/users/users.module.ts:

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { RouterModule } from '@angular/router';

// @angular/flex-layout
import { FlexLayoutModule } from '@angular/flex-layout';

// @angular/material
import {
  MdButtonModule,
  MdCardModule,
  MdIconModule,
  MdInputModule,
  MdProgressSpinnerModule,
  MdMenuModule,
} from '@angular/material';

// components
import { MyAccountComponent } from './my-account/my-account.component';
import { SignInComponent } from './sign-in/sign-in.component';
import { SignOutComponent } from './sign-out/sign-out.component';
import { SignUpComponent } from './sign-up/sign-up.component';

// routing
import { UsersRoutingModule } from './users-routing.module';

// components constant
const components = [
  MyAccountComponent,
  SignInComponent,
  SignUpComponent,
  SignOutComponent,
];

@NgModule({
  imports: [
    CommonModule,
    FlexLayoutModule,
    FormsModule,
    MdButtonModule,
    MdCardModule,
    MdIconModule,
    MdInputModule,
    MdProgressSpinnerModule,
    MdMenuModule,
    ReactiveFormsModule,
    RouterModule,
    UsersRoutingModule,
  ],
  declarations: components,
  exports: components,
})
export class UsersModule {}

Let’s review:

Phew - our routing is now configured and ready to go. So far we haven’t touched @ngrx, but we have our routing, modules and components set up and ready to go.

Store

To review, we created:

We also updated the AppModule class with our components and routing. Now, let’s add the StoreModule and RouterStoreModule to the src/app/app.module.ts file:

// @ngrx
import { RouterStoreModule } from '@ngrx/router-store';
import { StoreModule } from '@ngrx/store';

@NgModule({
  // code ommitted

  imports: [
    AppRoutingModule,
    BrowserModule,
    FormsModule,
    RouterStoreModule.connectRouter(),
    StoreModule.provideStore(reducer, {
      router: window.location.pathname + window.location.search,
    }),
  ],

  // code ommitted
})
export class AppModule {}

We import the RouterStoreModule and StoreModule, and then add them to the imports array in the NgModule decorator. I have also optionally set the initial router state.

Reducers

Reducers are pure functions that transform the state of our application.

What exactly does that mean?

The signature for the reducer function is:

function reducer(state: any = initialState, action: Actions): State

As you can see our reducer function accepts two parameters:

Before we implement our reducer function let’s declare what the state of my application will look like.

Create a new file: src/app/users/users.reducers.ts.

In the users.reducers.ts file we will first import the action, which we will create next, as well as the User model. Then, create the State interface:

// import actions
import { Actions, ActionTypes } from './users.actions';

// import models
import { User } from '../core/models/user';

/**
 * The state.
 * @interface State
 */
export interface State {
  // boolean if user is authenticated
  authenticated: boolean;

  // error message
  error?: string;

  // true if we have attempted existing auth session
  loaded: boolean;

  // true when loading
  loading: boolean;

  // the authenticated user
  user?: User;
}

Then, create the initialState object:

/**
 * The initial state.
 */
const initialState: State = {
  authenticated: false,
  loaded: false,
  loading: false,
};

By default we set the authenticated, loaded and loading properties to false.

Next, create the reducer function:

/**
 * The reducer function.
 * @function reducer
 * @param {State} state Current state
 * @param {Actions} action Incoming action
 */
export function reducer(state: any = initialState, action: Actions): State {
  switch (action.type) {
    case ActionTypes.AUTHENTICATE:
      return Object.assign({}, state, {
        loading: true,
      });

    case ActionTypes.AUTHENTICATED_ERROR:
      return Object.assign({}, state, {
        authenticated: false,
        error: action.payload.error.message,
        loaded: true,
      });

    case ActionTypes.AUTHENTICATED_SUCCESS:
      return Object.assign({}, state, {
        authenticated: action.payload.authenticated,
        loaded: true,
        user: action.payload.user,
      });

    case ActionTypes.AUTHENTICATE_ERROR:
    case ActionTypes.SIGN_UP_ERROR:
      return Object.assign({}, state, {
        authenticated: false,
        error: action.payload.error.message,
        loading: false,
      });

    case ActionTypes.AUTHENTICATE_SUCCESS:
    case ActionTypes.SIGN_UP_SUCCESS:
      const user: User = action.payload.user;

      // verify user is not null
      if (user === null) {
        return state;
      }

      return Object.assign({}, state, {
        authenticated: true,
        error: undefined,
        loading: false,
        user: user,
      });

    case ActionTypes.SIGN_OUT_ERROR:
      return Object.assign({}, state, {
        authenticated: true,
        error: action.payload.error.message,
        user: undefined,
      });

    case ActionTypes.SIGN_OUT_SUCCESS:
      return Object.assign({}, state, {
        authenticated: false,
        error: undefined,
        user: undefined,
      });

    case ActionTypes.SIGN_UP:
      return Object.assign({}, state, {
        authenticated: false,
        error: undefined,
        loading: true,
      });

    default:
      return state;
  }
}

In our reducer function we return a new state based on the incoming action. In this example I am using Object.assign() to return the transformed state object.

Next, let’s define some helper functions in our src/app/users/users.reducers.ts file. Each function will accept the State of our application and will return a specific value.

Add the following functions to the src/app/users/users.reducers.ts file:

/**
 * Returns true if the user is authenticated.
 * @function isAuthenticated
 * @param {State} state
 * @returns {boolean}
 */
export const isAuthenticated = (state: State) => state.authenticated;

/**
 * Returns true if the authenticated has loaded.
 * @function isAuthenticatedLoaded
 * @param {State} state
 * @returns {boolean}
 */
export const isAuthenticatedLoaded = (state: State) => state.loaded;

/**
 * Return the users state
 * @function getAuthenticatedUser
 * @param {State} state
 * @returns {User}
 */
export const getAuthenticatedUser = (state: State) => state.user;

/**
 * Returns the authentication error.
 * @function getAuthenticationError
 * @param {State} state
 * @returns {Error}
 */
export const getAuthenticationError = (state: State) => state.error;

/**
 * Returns true if request is in progress.
 * @function isLoading
 * @param {State} state
 * @returns {boolean}
 */
export const isLoading = (state: State) => state.loading;

/**
 * Returns the sign out error.
 * @function getSignOutError
 * @param {State} state
 * @returns {Error}
 */
export const getSignOutError = (state: State) => state.error;

/**
 * Returns the sign up error.
 * @function getSignUpError
 * @param {State} state
 * @returns {Error}
 */
export const getSignUpError = (state: State) => state.error;

Next, we will create a new file at: src/app/app.reducers.ts. I modeled this off of the @ngrx/example-app:

// reselect
import { createSelector } from 'reselect';

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

// environment
import { environment } from '../environments/environment';

/**
 * Every reducer module"s default export is the reducer function itself. In
 * addition, each module should export a type or interface that describes
 * the state of the reducer plus any selector functions. The `* as`
 * notation packages up all of the exports into a single object.
 */
import * as users from './users/users.reducers';

/**
 * We treat each reducer like a table in a database.
 * This means our top level state interface is just a map of keys to inner state types.
 */
export interface State {
  router: RouterState;
  users: users.State;
}

/**
 * Because metareducers take a reducer function and return a new reducer,
 * we can use our compose helper to chain them together. Here we are
 * using combineReducers to make our top level reducer, and then
 * wrapping that in storeLogger. Remember that compose applies
 * the result from right to left.
 */
const reducers = {
  router: routerReducer,
  users: users.reducer,
};

// development reducer includes storeFreeze to prevent state from being mutated
const developmentReducer: ActionReducer<State> = compose(
  storeFreeze,
  combineReducers
)(reducers);

// production reducer
const productionReducer: ActionReducer<State> = combineReducers(reducers);

/**
 * The single reducer function.
 * @function reducer
 * @param {any} state
 * @param {any} action
 */
export function reducer(state: any, action: any) {
  if (environment.production) {
    return productionReducer(state, action);
  } else {
    return developmentReducer(state, action);
  }
}

/**********************************************************
 * Users Reducers
 *********************************************************/

/**
 * Returns the user state.
 * @function getUserState
 * @param {State} state Top level state.
 * @return {State}
 */
export const getUsersState = (state: State) => state.users;

/**
 * Returns the authenticated user
 * @function getAuthenticatedUser
 * @param {State} state
 * @param {any} props
 * @return {User}
 */
export const getAuthenticatedUser = createSelector(
  getUsersState,
  users.getAuthenticatedUser
);

/**
 * Returns the authentication error.
 * @function getAuthenticationError
 * @param {State} state
 * @param {any} props
 * @return {Error}
 */
export const getAuthenticationError = createSelector(
  getUsersState,
  users.getAuthenticationError
);

/**
 * Returns true if the user is authenticated
 * @function isAuthenticated
 * @param {State} state
 * @param {any} props
 * @return {boolean}
 */
export const isAuthenticated = createSelector(
  getUsersState,
  users.isAuthenticated
);

/**
 * Returns true if the user is authenticated
 * @function isAuthenticated
 * @param {State} state
 * @param {any} props
 * @return {boolean}
 */
export const isAuthenticatedLoaded = createSelector(
  getUsersState,
  users.isAuthenticatedLoaded
);

/**
 * Returns true if the authentication request is loading.
 * @function isAuthenticationLoading
 * @param {State} state
 * @param {any} props
 * @return {boolean}
 */
export const isAuthenticationLoading = createSelector(
  getUsersState,
  users.isLoading
);

/**
 * Returns the sign out error.
 * @function getSignOutError
 * @param {State} state
 * @param {any} props
 * @return {Error}
 */
export const getSignOutError = createSelector(
  getUsersState,
  users.getSignOutError
);

/**
 * Returns the sign up error.
 * @function getSignUpError
 * @param {State} state
 * @param {any} props
 * @return {Error}
 */
export const getSignUpError = createSelector(
  getUsersState,
  users.getSignUpError
);

Let’s review the app.reducers.ts file:

At first this may seem a bit complicated, but it makes it easy to extend and build upon this design.

We now have our store configured with our reducers. The next step is to define our actions as well as any side effects from those actions that will change the state of our application.

Actions

With our Store, State and reducer() function set up, the next thing is to define our actions.

Broadly, we will need to perform the following actions:

  1. Authenticate the user from the sign in page.
  2. Determine if the user has an existing authenticated session.
  3. Terminate the session when the user signs out.
  4. Create a new user and authenticated session when the user signs in.

Using that as a base, let’s get started.

First, create a new file at: src/app/users/users.actions.ts. To start, import the necessary classes and then create a new ActionTypes object.

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

// import type function
import { type } from '../core/util';

// import models
import { User } from '../core/models/user';

export const ActionTypes = {
  AUTHENTICATE: type('[users] Authenticate'),
  AUTHENTICATE_ERROR: type('[users] Authentication error'),
  AUTHENTICATE_SUCCESS: type('[users] Authentication success'),
  AUTHENTICATED: type('[users] Authenticated'),
  AUTHENTICATED_ERROR: type('[users] Authenticated error'),
  AUTHENTICATED_SUCCESS: type('[users] Authenticated success'),
  SIGN_OUT: type('[users] Sign off'),
  SIGN_OUT_ERROR: type('[users] Sign off error'),
  SIGN_OUT_SUCCESS: type('[users] Sign off success'),
  SIGN_UP: type('[users] Sign up'),
  SIGN_UP_ERROR: type('[users] Sign up error'),
  SIGN_UP_SUCCESS: type('[users] Sign up success'),
};

First, note that I have imported the type() function from the src/app/core/util.ts file. This is a technique that I followed from the official @ngrx/example-app source code.

Here is our src/app/core/util.ts file with the type() function:

const typeCache: { [label: string]: boolean } = {};
export function type<T>(label: T | ""): T {
  if (typeCache[<string>label]) {
    throw new Error(`Action type "${label}" is not unique"`);
  }

  typeCache[<string>label] = true;

  return <T>label;
}

The type() function enforces the uniqueness of each ActionTypes property value. This is to help you as a developer and is not required.

We then use the type() function to declare our ActionTypes object, which is a constant object with string key and value pairs. If you look back to the reducer function you will note that we are switching on the incoming action based on the ActionTypes that we imported at the top of the src/app/users/reducers.ts file.

Next, we need to create a class for each action that we want to emit that implements the Action interface. Before we do that, however, let’s look at the Action interface:

export interface Action {
  type: string;
  payload?: any;
}

So, our task is to create classes that have a type property set to the ActionTypes value and a constructor function that will set the public payload property.

Let’s add these classes to our src/app/users/users.actions.ts file:

/**
 * Authenticate.
 * @class AuthenticateAction
 * @implements {Action}
 */
export class AuthenticateAction implements Action {
  public type: string = ActionTypes.AUTHENTICATE;

  constructor(public payload: {email: string, password: string}) {}
}

/**
 * Checks if user is authenticated.
 * @class AuthenticatedAction
 * @implements {Action}
 */
export class AuthenticatedAction implements Action {
  public type: string = ActionTypes.AUTHENTICATED;

  constructor(public payload?: {token?: string}) {}
}

/**
 * Authenticated check success.
 * @class AuthenticatedSuccessAction
 * @implements {Action}
 */
export class AuthenticatedSuccessAction implements Action {
  public type: string = ActionTypes.AUTHENTICATED_SUCCESS;

  constructor(public payload: {authenticated: boolean, user: User}) {}
}

/**
 * Authenticated check error.
 * @class AuthenticatedErrorAction
 * @implements {Action}
 */
export class AuthenticatedErrorAction implements Action {
  public type: string = ActionTypes.AUTHENTICATED_ERROR;

  constructor(public payload?: any) {}
}

/**
 * Authentication error.
 * @class AuthenticationErrorAction
 * @implements {Action}
 */
export class AuthenticationErrorAction implements Action {
  public type: string = ActionTypes.AUTHENTICATE_ERROR;

  constructor(public payload?: any) {}
}

/**
 * Authentication success.
 * @class AuthenticationSuccessAction
 * @implements {Action}
 */
export class AuthenticationSuccessAction implements Action {
  public type: string = ActionTypes.AUTHENTICATE_SUCCESS;

  constructor(public payload: { user: User }) {}
}

/**
 * Sign out.
 * @class SignOutAction
 * @implements {Action}
 */
export class SignOutAction implements Action {
  public type: string = ActionTypes.SIGN_OUT;
  constructor(public payload?: any) {}
}

/**
 * Sign out error.
 * @class SignOutErrorAction
 * @implements {Action}
 */
export class SignOutErrorAction implements Action {
  public type: string = ActionTypes.SIGN_OUT_SUCCESS;
  constructor(public payload?: any) {}
}

/**
 * Sign out success.
 * @class SignOutSuccessAction
 * @implements {Action}
 */
export class SignOutSuccessAction implements Action {
  public type: string = ActionTypes.SIGN_OUT_SUCCESS;
  constructor(public payload?: any) {}
}

/**
 * Sign up.
 * @class SignUpAction
 * @implements {Action}
 */
export class SignUpAction implements Action {
  public type: string = ActionTypes.SIGN_UP;
  constructor(public payload: { user: User }) {}
}

/**
 * Sign up error.
 * @class SignUpErrorAction
 * @implements {Action}
 */
export class SignUpErrorAction implements Action {
  public type: string = ActionTypes.SIGN_UP_ERROR;
  constructor(public payload?: any) {}
}

/**
 * Sign up success.
 * @class SignUpSuccessAction
 * @implements {Action}
 */
export class SignUpSuccessAction implements Action {
  public type: string = ActionTypes.SIGN_UP_SUCCESS;
  constructor(public payload: { user: User }) {}
}

We have created a class for each ActionTypes property with the corresponding payload object.

Finally, define a single Actions type that can any of the Action classes we just defined:

/**
 * Actions type.
 * @type {Actions}
 */
export type Actions =
  | AuthenticateAction
  | AuthenticatedAction
  | AuthenticatedErrorAction
  | AuthenticatedSuccessAction
  | AuthenticationErrorAction
  | AuthenticationSuccessAction
  | SignUpAction
  | SignUpErrorAction
  | SignUpSuccessAction;

If you look back to your reducer function in src/app/users/users.reducers.ts you will note that the incoming action argument is of type Actions.

Effects

According to the @ngrx/effects documentation:

In @ngrx/effects, effects are sources of actions.

Create a new file at: src/app/users/users.effects.ts:

import { Injectable } from "@angular/core";

// import @ngrx
import { Effect, Actions, toPayload } from "@ngrx/effects";
import { Action } from "@ngrx/store";

// import rxjs
import { Observable } from "rxjs/Observable";
import "rxjs/add/operator/debounceTime";
import "rxjs/add/operator/map";
import "rxjs/add/operator/switchMap";

// import services
import { UserService } from "../core/services/user.service";

// import models
import { User } from "../core/models/user";

// import actions
import {
  ActionTypes,
  AuthenticatedErrorAction,
  AuthenticatedSuccessAction,
  AuthenticationErrorAction,
  AuthenticationSuccessAction,
  SignOutErrorAction,
  SignOutSuccessAction,
  SignUpErrorAction,
  SignUpSuccessAction
} from "./users.actions";

/**
 * Effects offer a way to isolate and easily test side-effects within your
 * application.
 * The `toPayload` helper function returns just
 * the payload of the currently dispatched action, useful in
 * instances where the current state is not necessary.
 *
 * Documentation on `toPayload` can be found here:
 * https://github.com/ngrx/effects/blob/master/docs/api.md#topayload
 *
 * If you are unfamiliar with the operators being used in these examples, please
 * check out the sources below:
 *
 * Official Docs: http://reactivex.io/rxjs/manual/overview.html#categories-of-operators
 * RxJS 5 Operators By Example: https://gist.github.com/btroncone/d6cf141d6f2c00dc6b35
 */

@Injectable()
export class UserEffects {

  /**
   * Authenticate user.
   */
  @Effect()
  public authenticate: Observable<Action> = this.actions
    .ofType(ActionTypes.AUTHENTICATE)
    .debounceTime(500)
    .map(toPayload)
    .switchMap(payload => {
      return this.userService.authenticate(payload.email, payload.password)
        .map(user => new AuthenticationSuccessAction({ user: user }))
        .catch(error => Observable.of(new AuthenticationErrorAction({ error: error })));
    });

  /**
   * Determine if the user is authenticated.
   */
  @Effect()
  public authenticated: Observable<Action> = this.actions
    .ofType(ActionTypes.AUTHENTICATED)
    .map(toPayload)
    .switchMap(payload => {
      return this.userService.authenticatedUser()
        .map(user => new AuthenticatedSuccessAction({ authenticated: (user !== null), user: user }))
        .catch(error => Observable.of(new AuthenticatedErrorAction({ error: error })));
    });

  /**
   * Create a new user.
   */
  @Effect()
  public createUser: Observable<Action> = this.actions
    .ofType(ActionTypes.SIGN_UP)
    .debounceTime(500)
    .map(toPayload)
    .switchMap(payload => {
      return this.userService.create(payload.user)
        .map(user => new SignUpSuccessAction({ user: user }))
        .catch(error => Observable.of(new SignUpErrorAction({ error: error })));
    });

  /**
   * Terminate user session.
   */
  @Effect()
  public signOut: Observable<Action> = this.actions
    .ofType(ActionTypes.SIGN_OUT)
    .map(toPayload)
    .switchMap(payload => {
      return this.userService.signout()
        .map(value => new SignOutSuccessAction())
        .catch(error => Observable.of(new SignOutErrorAction({ error: error })));
    });

  /**
   * @constructor
   * @param {Actions }actions
   * @param {UserService} userService
   */
  constructor(
    private actions: Actions,
    private userService: UserService
  ) { }
}

Let’s review our import statements:

Let’s review the UserEffects class:

I will also note that in some instances I have used the debounceTime() rxjs operator. The goal of this was to show the loading indicator briefly, for one second, while we invoke the UserService methods to sign in or to sign up. As our current service is just a mock, the indicator would likely never show, or show for a mere millisecond. If you are implementing these effects in a production environment, be sure to remove the use of the debounceTime() operators.

Authentication Guard

With our store, actions and effects configured we are ready to implement the AuthenticationGuard. Guards enable us to protect routes by implementing the CanActivate and CanLoad interfaces.

To get started, create a new directory at: src/app/shared, and then create a new guard using the Angular CLI:

$ ng g guard shared/authentication

Here is our src/app/shared/authentication.guard.ts file:

import { Injectable } from "@angular/core";
import { ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot } from "@angular/router";

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

// import @ngrx
import { Store } from "@ngrx/store";
import { go } from "@ngrx/router-store";

// reducers
import {
  isAuthenticated,
  State
} from "../app.reducers";

/**
 * Prevent unauthorized activating and loading of routes
 * @class AuthenticatedGuard
 */
@Injectable()
export class AuthenticatedGuard implements CanActivate {

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

  /**
   * True when user is authenticated
   * @method canActivate
   */
  canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | boolean {
    // get observable
    const observable = this.store.select(isAuthenticated);

    // redirect to sign in page if user is not authenticated
    observable.subscribe(authenticated => {
      if (!authenticated) {
        this.store.dispatch(go("/users/sign-in"));
      }
    });

    return observable;
  }
}

Let’s review:

One important thing to note: you must use the the store as the single and only source for state management in your application. If you use the Angular RouterLink directive or the Router.navigate() methods your store will not know about the change in state. This can cause all kinds of oddities.

So, when using the store, stick with the store.

Sign In Component

OK, if you made it this far, then I would suggest you pat yourself on the back and get another cup of coffee!

Let’s dig into the SignInComponent. To get started, create a new component using the Angular CLI:

ng g component users/sign-in

Here is our SignInComponent:

import { Component, OnDestroy, OnInit } from "@angular/core";
import { FormBuilder, FormGroup, Validators } from "@angular/forms";

// @ngrx
import { Store } from "@ngrx/store";
import { go } from "@ngrx/router-store";

// rxjs
import { Observable } from "rxjs/Observable";
import "rxjs/add/operator/takeWhile";

// actions
import { AuthenticateAction } from "../users.actions";

// reducers
import {
  getAuthenticationError,
  isAuthenticated,
  isAuthenticationLoading,
  State
} from "../../app.reducers";

/**
 * /users/sign-in
 * @class SignInComponent
 */
@Component({
  templateUrl: "./sign-in.component.html",
  styleUrls: ["./sign-in.component.scss"]
})
export class SignInComponent implements OnDestroy, OnInit {

  /**
   * The error if authentication fails.
   * @type {Observable<string>}
   */
  public error: Observable<string>;

  /**
   * True if the authentication is loading.
   * @type {boolean}
   */
  public loading: Observable<boolean>;

  /**
   * The authentication form.
   * @type {FormGroup}
   */
  public form: FormGroup;

  /**
   * Component state.
   * @type {boolean}
   */
  private alive = true;

  /**
   * @constructor
   * @param {FormBuilder} formBuilder
   * @param {Store<State>} store
   */
  constructor(
    private formBuilder: FormBuilder,
    private store: Store<State>
  ) { }

  /**
   * Lifecycle hook that is called after data-bound properties of a directive are initialized.
   * @method ngOnInit
   */
  public ngOnInit() {
    // set formGroup
    this.form = this.formBuilder.group({
      email: ["", Validators.required],
      password: ["", Validators.required]
    });

    // set error
    this.error = this.store.select(getAuthenticationError);

    // set loading
    this.loading = this.store.select(isAuthenticationLoading);

    // subscribe to success
    this.store.select(isAuthenticated)
      .takeWhile(() => this.alive)
      .filter(authenticated => authenticated)
      .subscribe(value => {
        this.store.dispatch(go("/users/my-account"));
      });
  }

  /**
   *  Lifecycle hook that is called when a directive, pipe or service is destroyed.
   * @method ngOnDestroy
   */
  public ngOnDestroy() {
    this.alive = false;
  }

  /**
   * Go to the home page.
   * @method home
   */
  public home() {
    this.store.dispatch(go("/"));
  }

  /**
   * To to the sign up page.
   * @method signUp
   */
  public signUp() {
    this.store.dispatch(go("/users/sign-up"));
  }

  /**
   * Submit the authentication form.
   * @method submit
   */
  public submit() {
    // get email and password values
    const email: string = this.form.get("email").value;
    const password: string = this.form.get("password").value;

    // trim values
    email.trim();
    password.trim();

    // set payload
    const payload = {
      email: email,
      password: password
    };

    // dispatch AuthenticationAction and pass in payload
    this.store.dispatch(new AuthenticateAction(payload));
  }
}

Let’s review our SignInComponent:

Now, let’s put this together with our template in src/app/users/sign-in/sign-in.component.html:

<div class="flex-container" fxLayout="column">
  <button (click)="home()" fxFlexAlign="center" class="logo-container">
    <div class="logo"></div>
  </button>
  <div fxLayout="row" fxLayoutAlign="center" class="card-container">
    <div fxFlex="90%" fxFlex.md="50%" fxFlex.lg="40%">
      <md-card>
        <md-card-title>Sign In</md-card-title>
        <md-card-content>
          <div *ngIf="error | async" class="error">{{ error | async }}</div>
          <form
            *ngIf="!(loading | async)"
            (ngSubmit)="submit()"
            [formGroup]="form"
            novalidate
          >
            <div fxLayout="column">
              <md-input-container fxFlex="100%">
                <input
                  mdInput
                  required
                  placeholder="Email Address"
                  autocorrect="off"
                  autocapitalize="off"
                  spellcheck="off"
                  formControlName="email"
                  [class.invalid]="form.controls.email.errors && form.controls.email.dirty"
                />
              </md-input-container>
              <md-input-container fxFlex="100%">
                <input
                  mdInput
                  required
                  type="password"
                  placeholder="Password"
                  autocomplete="off"
                  autocorrect="off"
                  autocapitalize="off"
                  spellcheck="off"
                  formControlName="password"
                  [class.invalid]="form.controls.password.errors && form.controls.password.dirty"
                />
              </md-input-container>
              <button
                md-raised-button
                color="accent"
                [disabled]="form.invalid"
                type="submit"
              >
                Sign In
              </button>
              <a md-button (click)="signUp()">Sign Up with Email</a>
            </div>
          </form>
          <div fxLayout="row" fxLayoutAlign="center">
            <md-spinner *ngIf="loading | async"></md-spinner>
          </div>
        </md-card-content>
      </md-card>
    </div>
  </div>
</div>

Our HTML template uses Flex Layout and Angular Material for the layout and UI. In general:

Now, let’s style our page using Sass in the src/app/users/users.component.scss file:

@import '~@angular/material/core/theming/palette';
@import '~@angular/material/core/theming/theming';
@import '../../../sass/mixins';

:host {
  .flex-container {
    background-color: mat-color($mat-grey, 100);
    position: relative;
    top: 0;
    left: 0;
    right: 0;
    min-height: 100vh;

    &::after {
      position: absolute;
      top: 0;
      width: 100%;
      height: 200px;
      left: 0;
      right: 0;
      background-color: mat-color($mat-indigo, 500);
      content: '';
      box-shadow: 0 2px 4px 1px rgba(0, 0, 0, 0.14);
      z-index: 0;
    }

    .logo-container,
    .card-container {
      z-index: 1;
    }

    button.logo-container {
      background: transparent;
      padding: 0 16px;
      box-sizing: border-box;
      cursor: pointer;
      user-select: none;
      outline: 0;
      border: none;
      display: inline-block;
      white-space: nowrap;
      text-decoration: none;
      vertical-align: baseline;

      .logo {
        height: 64px;
        width: 64px;
        margin: 48px auto;
        background: url('http://placehold.it/64x64') contain;
        @include retina {
          background: url('http://placehold.it/128x128') 64px/64px;
        }
      }
    }

    .card-container {
      md-card {
        background-color: #fff;
        margin-bottom: 16px;

        md-card-title {
          text-transform: uppercase;
          text-align: center;
          margin-bottom: 48px;
        }

        md-card-content {
          margin-bottom: 16px;

          .error {
            margin: 16px 0;
            color: mat-color($mat-pink, 500);
          }

          md-input-container {
            margin-bottom: 16px;
          }

          button,
          a {
            margin-top: 16px;
          }
        }
      }
    }
  }
}

A few things to note:

Dev Tools

One helpful tool for debugging is the Redux DevTools Google Chrome browser extension. The DevTools enable us to:

To get started download and install the Redex DevTool extension. Next, we will need to install the @ngrx/store-devtools package:

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

Then, modify your AppModule:

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

@NgModule({
  imports: [
    StoreDevtoolsModule.instrumentOnlyWithExtension()
  ]
})

Note, you must import the StoreDevtoolsModule after the StoreModule.

There is a new tab in Chrome’s Developer Tools called Redux. Here is a screen shot showing the state change after attempting to load our application with not path.

Redux DevTools

Conclusion

A couple of things that I have learned:

  1. Read the docs.
  2. Refer to the example-app
  3. Do not try to circumvent the store. All routing and state changes must happen in the store.