In this blog I’ll show you how to guard routes in Angular 2 Router.
Let’s consider some scenarios that require a certain validation to be performed to decide if the user (or a program) is allowed to navigate to or leave the route:
* Allow to open the route only if the user is authenticated and authorized to do so.
* Implement a multi-part form that consists of several components, and the user is allowed to navigate to the next form section only if the data entered in the current one is valid.
* Remind the user about the unsaved changes if he or she tries to navigate from the route.
The router has the hooks that give you more control over the navigation to/from a route, and you can use these hooks to implement the any of above scenarios to guard the routes.
Angular includes a number of component lifecycle hooks that allow you to handle important events in the life of a component. First, it worth noting that the route configuration is done outside of the components. You configure routes in an object of type RouterConfig, then give it to the provideRouter() function, which in turn is given to the function bootstrap() that loads your app.
The type RouterConfig is a collection of items that conforms to the Route interface shown below:
export interface Route { path?: string; pathMatch?: 'full' | 'prefix'; component?: Type | string; redirectTo?: string; outlet?: string; canActivate?: any[]; canDeactivate?: any[]; data?: Data; resolve?: ResolveData; children?: Route[]; }
While configuring routes you’ll typically use two properties from this interface: path and component. For example, an app that has two links Home and Product Details can specify and bootstrap the routes as follows:
bootstrap(RootComponent, [ provideRouter([ {path: '', component: HomeComponent}, {path: 'product', component: ProductDetailComponent}]), {provide: LocationStrategy, useClass: HashLocationStrategy} ]);
But in this blog I’d like to you to get familiar with the properties canActivate and canDeactivate that allow you to hook up the routes with the guards. Basically you need to write a function(s) implementing the validating logic that will return either true or false and assign it to one of these properties. If canActivate() of the guard returns true, the user can navigate to the route. If canDeactivate() returns true, the user can navigate from the route. Since both canActivate and canDeactivate properties of Route accept an array as a value, you can assign multiple functions (the guards) if you need to check more than one condition to allow or forbid the navigation.
Let’s create a simple app with Home and Product Details links to illustrate how you can protect the product route from the users who are not logged in. To keep the example simple, we won’t use an actual login service, but will generate the login status randomly.
We’ll create a guard class that implements the interface CanActivate, which declares only one function to implement: canActivate(). This function should contain the application logic that returns true or false. If the function returns false (the user is not logged in) the application will not navigate to the route and will print the error message on the console.
import {CanActivate} from "@angular/router"; import {Injectable} from "@angular/core"; @Injectable() export class LoginGuard implements CanActivate{ canActivate() { return this.checkIfLoggedIn(); } private checkIfLoggedIn(): boolean{ // A call to the actual login service would go here // For now we'll just randomly return true or false let loggedIn:boolean = Math.random() < 0.5; if(!loggedIn){ console.log("LoginGuard: The user is not logged in and can't navigate product details"); } return loggedIn; } }
As you see from the code, my implementation of the function canActivate() will randomly return true or false emulating the user’s logged in status.
The next step is to update the router configuration so it uses our guard. The code snippet below shows how the function provideRouter() can look like for the app that has Home and Product Detail routes and the latter is protected by our LoginGuard:
provideRouter([ {path: '', component: HomeComponent}, {path: 'product', component: ProductDetailComponent, canActivate:[LoginGuard]} ])
Adding one or more guards to the array given to the canActivate property will automatically invoke all guards one after the other. If any of the guards returns false, the navigation to the route will be prohibited.
But who will instantiate the class LoginGuard? Angular will do it for us using its dependency injection mechanism, but you have to mention this class in the list of providers which are needed for injection to work. We’ll just add the name LoginGuard to the list of providers in the bootstrap() function of our app:
bootstrap(RootComponent, [ provideRouter([ {path: '', component: HomeComponent}, {path: 'product', component: ProductDetailComponent, canActivate:[LoginGuard]}]), LoginGuard, {provide: LocationStrategy, useClass: HashLocationStrategy} ]);
The complete code of the main app script is shown next:
import {bootstrap} from '@angular/platform-browser-dynamic'; import {Component} from '@angular/core'; import {LocationStrategy, HashLocationStrategy} from '@angular/common'; import {provideRouter, ROUTER_DIRECTIVES} from '@angular/router'; import {HomeComponent} from './components/home'; import {ProductDetailComponent} from './components/product'; import {LoginGuard} from './guards/login.guard'; @Component({ selector: 'basic-routing', directives: [ROUTER_DIRECTIVES], template: ` <a [routerLink]="['/']">Home</a> <a [routerLink]="['/product']">Product Details</a> <router-outlet></router-outlet> ` }) class RootComponent {} bootstrap(RootComponent, [ provideRouter([ {path: '', component: HomeComponent}, {path: 'product', component: ProductDetailComponent, canActivate:[LoginGuard]}]), LoginGuard, {provide: LocationStrategy, useClass: HashLocationStrategy} ]);
If you run this app and will try to lick on the Product Details link, it’ll either navigate to this route or print the error message on the browser console depending on the randomly generated value in the LoginGuard. The snapshot below was taken after the user tried to click on the Product Details link, but the LoginGuard “decided” that the user is not logged in.
But if our unpredictable LoginGuard “decided” that the user is logged in, the screen will look as follows after clicking on the Product Details link:
Dependency Injection Benefit. Since a guard is injected into your app, you can use it as any other object. For example, you can inject it in your RootComponent:
constructor (private _loginGuard:LoginGuard){}
Then invoke any methods defined on the guard class, e.g.:
this._loginGuard.changeLoginStatus(true);
Our LoginGuard implements the method canActivate() without providing any arguments to it. But this method can be used with the following signature:
canActivate(destination: ActivatedRouteSnapshot, state: RouterStateSnapshot)
The values of the ActivatedRouteSnapshot and RouterStateSnapshot will be injected by Angular automatically and may become quite handy if you want to analyze the current state of the router. For example, if you’d like to know the name of the route the user tried to navigate to, this is how to do it:
canActivate(destination: ActivatedRouteSnapshot, state: RouterStateSnapshot) { console.log(destination.component.name); ... }
Implementing the CanDeactivate interface that would control the process of navigating from a route works similarly. Just create a guard class that implements the method canDeactivate(), for example:
import {CanDeactivate, Router} from "@angular/router"; import {Injectable} from "@angular/core"; import {ProductDetailComponent} from "../product.component"; @Injectable() export class UnsavedChangesGuard implements CanDeactivate<ProductDetailComponent>{ constructor(private _router:Router){} canDeactivate(component: ProductDetailComponent){ return window.confirm("You have unsaved changes. Still want to leave?"); } }
Don’t forget to add the canDeactivate property to the route configuration and inject the new guard in bootstrap(), e.g.:
bootstrap(RootComponent, [ provideRouter([ {path: '', component: HomeComponent}, {path: 'product', component: ProductDetailComponent, canActivate:[LoginGuard], canDeactivate:[UnsavedChangesGuard]} ]), LoginGuard, UnsavedChangesGuard, {provide: LocationStrategy, useClass: HashLocationStrategy} ]);
For a fancier way of displaying confirmation dialogs use the MdDialog component from the Material Design 2 library (see https://github.com/angular/material2). This component will be released in the upcoming Alpha 8.
NOTE: If you want to delay the navigation to a route until certain data structures have been populated, use the property resolve from the Route interface. I’ll write a separate blog showing how to do it.
In this blog I didn’t show you the code of HomeComponent and ProductDetail component, but you can find them in the Github repo with the code samples from Chapter 3 from our book Angular 2 Development with TypeScript.
If you’re interested in learning Angular 2 in depth, enroll into one of our workshops. The next one we’ll run online starting from September 11, 2016.
Hi Yakov! Hanks for the article!
I have a question: I want to run my Angular 2 project on the google cloud. Which application server should I use?
First, decide if you really need an app server. Take a look at the FIrebase and AngularFire 2.
I created a compute engine instance and it has a external IP. As I understand I need to install an application server(like apach or denver) to receive requests to these ip. Or i din’t understan something?
My Back-end is implemented on Java and MySQL.
Thanks a lot! I was not aware of the canDeactivate property.
Hi Yakov! Great post! I have a question. Why is a simple application size near 1 Mb? It’s a lot as for a simple web application. What do you think? Do you have same solution for reducing size to near 100-200 Kb?
Yes, if you build this app with Webpack the size will go down to under 200KB. If you’ll also use Ahead-of-Time compilation, the app size will go down to 100KB.
Hi Yakov,
I’m trying to use canActivate to see if a user has rights to move to a specific screen. The component is a child of a super class I have. When I use instanceOf, it never returns true. I think my syntax may be wrong. Here is my canActivate method below. The printout from console.log(route.component.valueOf()) does show the value of the activityCode property. Hopefully you can set me straight or send me to a reference in your book.
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
console.log(“app-authGuard.canActivate”);
console.log(route.component.valueOf());
if (route.component instanceof ViewScreenComponent) {
let activityCode = (route.component as ViewScreenComponent).activityCode;
console.log(activityCode);
if (userHasActivityCode(activityCode)) {
return true;
}
}
return false;
}
Thanks,
Jim
Inheriting components was introduced like three weeks ago. I’m not sure what would be the type of the ActivatedRoute.component.
Just put a breakpoint in canActivate() in Chrome Dev Tools and see what’s the type of the ActivatedRoute.component.
Instead of using instanceOf you can try
if (route.component.name==="ViewScreenComponent")
Thanks, I’ll give that a shot. Am I going in the right direction for controlling what screen a user can access? I tried just disabling the menu item but the link would still trigger the navigation.
yes, using guards is fine here
Thanks for your help. Very much appreciated.
I am so close but struggling to get the information I need from the component. The component property of the ActivatedRouteSnapshot class is defined as component : Type|string. When I log the ActivatedRouteSnapshot param of canActivate() to the console, the component property looks like this:
component: function ManualTransactionsComponent()
arguments:(…)
caller:(…)
length:0
name:”ManualTransactionsComponent”
prototype:ViewScreenComponent
And when I log the component property to the console, it looks like this:
function ManualTransactionsComponent() {
_super.call(this);
this.activityCode = ‘ManualTransactions’;
}
I am trying to get to the protoype property value and the activityCode value but hitting a dead end. It looks like it is a pipe but I don’t know how to get to those values.
Just tried it – works fine. I created a TestComponent with a property test. Then extended another component from TestComponent and reached the property value from the ancestor:
export class ProductDetailComponentParam extends TestComponent{
productID: number;
constructor(route: ActivatedRoute) {
super();
console.log(this.test);
Yes, I can get it through the child but my problem is getting it out of the component property of the ActivatedRouteSnapshot class. The component property is defined as a Type|string . Can you give me the syntax for that?
Type|string means that the value in the component property can be either an object of some known type or a string.
I’m not sure why are you trying to get the object’s property via the ActivatedRoutSnapshot, which is used to get something specific to the navigation, e.g. parameters that are being passed to the component under route during the navigation.
If you just need to get a value of the component’s property you don’t need to use ActivatedRoute.
I’m going through a CanActivate guard when the user tries to navigate to that screen so somehow I need to see what screen the app is going to and pull out the activityCode of that screen to compare to the users roles. Example is below:
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
console.log(“app-authGuard.canActivate”);
console.log(route);
console.log(route.component);
if (route.component instanceof ViewScreenComponent) {
let activityCode = (route.component as ViewScreenComponent).activityCode;
console.log(activityCode);
if (hasAcess(activityCode)) {
return true;
}
}
return false;
}
If the app is in the process of navigating to that screen, the destination component doesn’t exist yet on the DOM. You can’t query its properties.
Have a service (not a component) that holds the user’s role and query it in the guard.
Should say Type|string
I guess the comments don’t accept ” ‘less than symbol’ any ‘greater than symbol’ “
Thanks for your assistance Yakov. I ended up passing the activityCode in the router data property to the guard. That did the trick.
The router data property is specified during the routes configuration and its value remains the same during the runtime. See if this is fine with you.
That should be ok since this value is constant. I then use that value to interact with a authorization service.
Hello there!!, Is there any way to route the content into another component so that I could hid the previous content and display the content of my new event.
Fetching the data from API and hide* and show the required content is main focus of my application with a back button. Could you please give the Idea. I have loaded the whole content in my single page and it seems like the dustbin
Hi Yakov,
I have implemented a CanActivate guard.
I’m using it for a mobile web application that I’ve created. We found when dialogs were being opened that people were pressing the phone back button to close the dialog and this was taking them to the previous page.
The guard that I’ve created sets CanActivate to false when a dialog is opened and true when it is closed. This allows the user to use the back button and they will appear to stay on the same page. However, if they do this a few times, the route eventually gets to a point before the first time the website was visited and then proceeds to that website.
I’m not sure if I’ve explained the problem very well but would be grateful if you had any insight in the matter.
Many thanks,
Luke
I’ve implemented the CanDeactivate() method based on your tutorial; returning true when the user confirms ‘Yes’, but once that happens I can’t navigate to other links in the application
Hello, Thx for the tutorial.
I have a small question, which is to show a static loading page while user is authenticated.
note i’m using lazy loading pattern, basically load a static page is the concern here