In this part, we’ll use Firebase for establishing roles in our system, setting up rules in firebase for security and routes in our app.

This is Part 6 of our Real World Angular series. For other parts, see links at the bottom of this post or go to https://blog.realworldfullstack.io/. The app is still in development. Check it out @ https://bitwiser.io.

Routing revisited — Route Guards

In our prior part (Part 5), we setup authentication in our app, but we did not really restrict any functionality based on authentication. The users were still able to add new questions without logging in. We need to ensure the user is authenticated before navigating to the add questions route. There might be other scenarios where we need to perform some checks or fetch additional data before navigating to a route (or navigating away from a route). Angular allows you to have pre and post processing for routes using route guards - https://angular.io/docs/ts/latest/guide/router.html#!#guards

The router supports the following kinds of guards (taken from the docs) -

CanActivate to mediate navigation to a route.

CanActivateChild to mediate navigation to a child route.

CanDeactivate to mediate navigation away from the current route.

Resolve to perform route data retrieval before route activation.

CanLoad to mediate navigation to a feature module loaded asynchronously.

CanActivate route guard

To ensure only authenticated users can access our ‘question/add’ route, we’ll use the CanActivate route guard. (Starting from where we left off - Part 5 Repo).

Let’s add an auth-guard to our services folder -

// auth-guard.ts import { Injectable } from '@angular/core';

import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';

import { AuthenticationService } from './authentication.service'; @Injectable()

export class AuthGuard implements CanActivate {

constructor(private authService: AuthenticationService) { }

canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {

if (!this.authService.isAuthenticated){

this.authService.showLogin(state.url);

return false;

} return true;

}

}

The canActivate method checks if the user is authenticated. If not, it invokes the showLogin method on the auth service and returns false. If the user is authenticated, it returns a true which indicates that the user can redirect to this route.

Note that we’re passing in the state.url from the RouterStateSnapshot, so we can redirect the user to the route requesting the authentication after a successful login.

Since, login can be invoked independent of the router, the url needs to be optional. We also need a way to save this url, since we might have several component interactions from the time the method is invoked, until the actual login. We’ll save the url in our store. Let’s create a new reducer in the app store called ui-state. For now, we’ll just add our redirect url to this, but this could be later extended to save more shared state about our ui.

We’ll add our ui-state.action -

// ui-state.actions.ts import {Injectable} from '@angular/core';

import {Action} from '@ngrx/store'; @Injectable()

export class UIStateActions {

static LOGIN_REDIRECT_URL = 'LOGIN_REDIRECT_URL';

setLoginRedirectUrl(url?: string): Action {

return {

type: UIStateActions.LOGIN_REDIRECT_URL,

payload: url

};

}

}

and the ui-state.reducer -

// ui-state.reducer.ts import { Observable } from 'rxjs/Observable';

import '../../rxjs-extensions';

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

import { UIStateActions } from '../actions'; export const loginRedirectUrl = (state: any = null, action: Action): string => {

switch (action.type) {

case UIStateActions.LOGIN_REDIRECT_URL:

return action.payload;

default:

return state;

}

};

Let’s add these to the appropriate index files in the services, action and reducer folders and add the reducer to our app-store.

We’ll modify our showLogin function of the authentication service to save the redirect url to our state -

// authentication.service.ts ...

import { UserActions, UIStateActions } from '../store/actions';

... export class AuthenticationService {

constructor(...

private uiStateActions: UIStateActions,

...) {

... showLogin = function(url?: string) {

this.store.dispatch(this.uiStateActions.setLoginRedirectUrl(url));

this.dialogRef = this.dialog.open(LoginComponent, {

disableClose: false

});

};

...

We’ll modify our app.component to perform the redirect on login, if a loginRedirectUrl is set in the store -

// app.component.ts constructor(...) {

...

this.sub2 = store.select(s => s.user).subscribe(user => {

this.user = user

if (user)

{

let url: string;

this.store.take(1).subscribe(s => url = s.loginRedirectUrl);

if (url)

this.router.navigate([url]);

}

});

}

...

And finally, let’s bind our auth guard to the appropriate route(s).

// app.route.ts import { AuthGuard } from './services'; ...

{

path: 'question/add',

component: QuestionAddUpdateComponent,

canActivate: [AuthGuard]

}

];

We’ll add our new action (UIStateActions) to the app.module.ts and test it out. You’ll see that the route guard prevents us from navigating to the add questions route unless we login.