Brian Love
Angular + TypeScript Developer in Denver, CO

Angular Reactive (ngrx) Authentication

Reading time ~35 minutes

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:

  • Add authentication to our application using an authentication guard.
  • Use @ngrx/store for state management.
  • Use @ngrx/effects to isolate our side effects.
  • Use @ngrx/router-store to bind the Angular router to our store.
  • Have fun!

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:

  • @angular/flex-layout
  • @angular/material and @angular/animations
  • @ngrx/core
  • @ngrx/store
  • @ngrx/effects
  • @ngrx/router-store
  • ngrx-store-freeze
  • reselect
$ 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:

  • First, I am importing the necessary classes and functions.
  • Next, I have defined a MOCK_USER object that will be used in my service. As I indicated before, this will eventually integrate with a remote REST API using MongoDb and Mongoose, but in the meantime, we will just mock the service methods.
  • I decorate the UserService using the Injectable() decorator so that the UserService can be injected into my UserEffects class.
  • A private boolean variabled named _authenticated will keep track of whether or not my user is authenticated.
  • The authenticate() method is invoked when the user is attempting to authenticate supplying their credentials. The method returns an Observable of type User.
  • The authenticated() method is used to determine if the user has an existing authenticated session in my application server (whatever backend you choose). Often times our user is coming back to our application, and they don’t want to be pestered with supplying their credentials each time. This method returns an Observable that is a boolean value; true if they are authenticated, or false if not.
  • The authenticatedUser() method is used to return an Observable of our authenticated User.
  • The create() method is invoked when the user is signing up for a new account.
  • The signOut() method terminates the user’s session.

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:

  • As always, we import the necessary classes, including the NotFoundComponent that will be displayed when a user accesses a route path that is invalid.
  • Next, we set up our routes array.
  • First, I lazy load the UsersModule.
  • Next, we redirect a root request (with no path) to the /users/my-account page. Likely you will want to redirect to another route. This is just for simplicity (not having to create an additional module for this example).
  • Next, we set up a route for the “404” path.
  • Then, we redirect all routes that are not explicitly defined, using the wildcard symbol (double asterisks), to the 404 page.
  • Finally, we wire it all up using the RouterModule.forRoot() method. Keep in mind that the forRoot() method should only be used in your base application routing.

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 = [
  {
    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:

  • As always, we import the necessary classes, including the AuthenticationGuard. We’ll take a look at that after we go through the ngrx store, actions and effects.
  • Next, we set up our routes. Note that the “my-account” route is protected using the CanActivate property, where we specify the AuthenticationGuard class. There are also routes for “sign-in”, “sign-out”, and “sign-up”. We will not go through each component, but we will review the SignUpComponent at the end.
  • Finally, we wire it all together in our UsersRoutingModule that is decorated with NgModule. Note that we invoke the forChild() method on the RouterModule as this is not the root module.

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 { }
  • First, I have added import statements for the following modules: BrowserAnimationsModule, AppRoutingModule, NotFoundComponent and UserService.
  • Next, I added the NotFoundComponent to the declarations array.
  • Then, I added the AppRoutingModule and FormsModule to the imports array.
  • Finally, I added the UserService to the providers array.

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:

  • First, we import the FormsModule and the ReactiveFormsModule classes. We’ll need these for our sign up and sign in forms. Add these to the imports array.
  • Next, import the RouterModule and add it to the imports array.
  • Then, import the FlexLayoutModule and add it to the imports array.
  • Then, import all of the Angular Material modules that we will need. In my example I am importing the button, card, icon, input, progress spinner and menu modules. Again, add these to the imports array.
  • Then, import the components in our module. I created a components constant array to store reference to the array of components in the module. This is used in both the declarations and exports properties of the NgModule decorator.
  • Finally, import the UsersRoutingModule and add this to the imports array.

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:

  • The User model and UserService service.
  • The AppRoutingModule and UsersRoutingModule modules with our routing configurations.
  • The UsersModule for our /users module with our components, Angular Material and routing.

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?

  • A pure function will always return the same value given the same input.
  • We will define what the state of our application looks like by defining a State interface.
  • We will implement the State interface in a new initialState object, which is the default state that is given to our reducer function.
  • Our reducer function will transform the state of our application based on an action that is emitted.

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:

  • state - the state of our application. The default value is the initialState.
  • action - the action that has been emitted. We will look more at actions next.

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:

  • First, we import the createSelector() function from the reselect project. Reselect is a selector library for Redux, which we can use with @ngrx to create functions that we will pass to the store.select() method. The use of reselect is optional. In place of the selectors that createSelector() creates you can simply hard code a string constant when using store.select()
  • Next, we import several classes from the @ngrx packages.
  • We import the ActionReducer interface and the combineReducers() function. We will use the combineReducers() function for our production reducer
  • We import the compose() function that we will use in development to ensure that our state is immutable.
  • We import the routerReducer() reducer function and RouterState interface from the @ngrx/router-store package.
  • We import the storeFreeze() function that will be used in development.
  • Finally, we import the environment object to determine if we are running in production mode or not.
  • From there, we create the top level State interface and the top level reducers object. If you revisit the src/app/app.module.ts file you will note that we provide the reducers to the StoreModule.provideStore() method in our imports array.
  • Next, we create a getUsersState() helper method that returns the user State.
  • Finally, we create reselect functions that we will pass to the store’s select() operator. We will see this implemented later in the AuthenticationGuard class.

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:

  • First, import the Injector decorator so that our UserEffects class can be injected using the Angular dependency injection framework.
  • Then, import the Effect decorator. We will use this to decorate the functions that are sources of actions.
  • Then, import the Actions observable. This observable emits every Action that is dispatched in your application.
  • Then, import the toPayload() function. This function returns the payload for the current action.
  • Next, import the Observable class as well as some rxjs operators.
  • Next, import the UserService, which be injected into our UserEffects class.
  • Next, import the User model as some of our actions will return an Observable of the authenticated User.
  • Finally, we import all of the actions that we defined in our src/app/users/users.actions.ts module.

Let’s review the UserEffects class:

  • First, we decorate our UserEffects class using the @Injectable() decorator.
  • Next, we define public properties for each effect, which is decorated using @Effect().
  • Each property is an Observable of an Action.
  • We use the ofType() operator to filter the Actions.
  • We then use the map() operator specifying the toPayload() helper function to get the payload for the current action.
  • Finally, we use the switchMap() operator to accept the payload and to return a new Observable object.
  • Inside switchMap() we return the Observable from the appropriate method in the UserService class, which in turn, uses the map() operator to emit a new action. In most cases, we are emitting the success action with a catch block to emit a new Observable.of() the Error.
  • Note that we provide a payload object for each action as required in the constructor of the action class that we defined in the src/app/users/users.actions.ts module.

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:

  • We import the ActivatedRouteSnapshot class, CanActivate interface and RouterStateSnapshot class.
  • Then, import the Observable class. Our canActivate() method is asynchronous. While our service is currently mocked, in the future, our UserService will implement an HTTP REST API, which by nature of HTTP, is asynchronous. As such, we will use Observables.
  • Next, import the Store as well as the go() function. We will need access to the Store to determine if the user is authenticated or not. And, the go() function will enable us to update our application’s router state.
  • The AuthenticationGuard will implement the CanActivate interface by implementing the canActivate() method.
  • The canActivate() method is provided the activate route snapshot as well as the state tree of activated routes. I am not using these in my canActivate() method because I am going to protect all routes that use the AuthenticationGuard.
  • We inject the Store into our class using the constructor() function.
  • And then we use the select() method to return an Observable<boolean> to determine if the user is authenticated.
  • I then subscribe() to the observable, and if the user is not authenticated, they are sent to the sign in page via the store’s dispatch() method.
  • Finally, I return the observable.

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:

  • First, we import the necessary classes from the @angular/core, @angular/forms, @ngrx/store, @ngrx/router-store and rxjs packages.
  • Next, import the AuthenticateAction. We will dispatch this action when our user attempts to authenticate, providing the necessary payload object containing the user’s email and password.
  • Next, import the getAuthenticationError(), isAuthenticated() and isAuthenticationLoading() reselectors from the src/app/app.reducers.ts module.
  • Also, import our top level State interface from src/app/app.reducers.ts.
  • Note that the SignInComponent class implements both the OnDestroy and OnInit interfaces. As required for these interfaces, our component will implement the ngOnDestroy() and ngOnInit() lifecycle methods.
  • The public error property is an Observable that we can subscribe to via the async pipe to show the user any error that has occured. For example, if their authentication attempt fails due to incorrect credentials.
  • The public loading property is an Observable that we can subscribe to via the async pipe to show a loading indicator.
  • The public form property is the FormGroup for the sign in form.
  • And, the private alive boolean flag indicates if our component is alive or destroyed.
  • The formBuilder and Store are injected in our constructor() function.
  • In the ngOnInit() method we build the FormGroup using the formBuilder, we set the error and loading observables, and subscribe to the isAuthenticated() selector. When the user has been successfully authenticated we go to the /users/my-account page.
  • The ngOnDestroy() method toggles the value of the private alive boolean to false to unsubscribe our observables.
  • The home() method goes to the home page.
  • The signUp() method goes to the sign up page.
  • Finnaly, the submit() method dispatches a new AuthenticateAction action with the necessary payload.

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:

  • I have a .flex-container element that wraps the content and provides some background.
  • There is a logo at the top, and when clicked on, will take you to the application’s home page.
  • Beneath the logo we use the md-card component to contain the authentication form and buttons.
  • The form uses the md-input-container component along with the mdInput directive to style the form inputs.
  • I also use the md-button component to style the buttons.
  • Finally, an md-spinner is used to display an indeterminate loading indicator.

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,.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:

  • We are importing Material’s palette library.
  • We are importing Material’s theming, including the mat-color() function. This way I can use the color palettes that Material includes.
  • I have a placeholder graphic for the logo, which uses a retina mixin that is defined in the src/sass/_mixins.scss file.

Dev Tools

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

  • Inspect state.
  • View state changes.
  • Record, play and pause state changes in my application.

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.

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).