Angular Route Security & Guards Tutorial
In this Angular tutorial we learn how to restrict access to certain components by using Guards that execute code before a user goes through a Route.
We cover the main types of Guards. CanActivate, CanDeactivate and Resolvers.
What are Route Guards
A guard is a section of code that’s executed before the user goes through a route, either navigating to a component or away from it.
For example, we restrict a user’s access to the member area before they have been authenticated through the login system. Or, if a user has pending changes that has to be saved before they can navigate away from an options component.
Lesson Project Setup
Before we start learning about guards, let’s set up our project to help in the demonstration.
We want the following:
- Two components called ‘home’ and ‘user’.
- A simple menu with router links to the home page and the user page.
- A router outlet in the main ‘app’ component that will load the other components.
1. First, we’ll generate the ‘home’ and ‘user’ components.
ng generate component home
ng generate component user
2. Next, we’ll create the routes to the ‘home’ and ‘user’ pages.
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
import { HomeComponent } from './home/home.component';
import { UserComponent } from './user/user.component';
import { Routes, RouterModule } from '@angular/router';
const appRoutes: Routes = [
{ path: '', component: HomeComponent },
{ path: 'user', component: UserComponent }
];
@NgModule({
declarations: [
AppComponent,
HomeComponent,
UserComponent
],
imports: [
BrowserModule,
RouterModule.forRoot(appRoutes)
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
3. Finally, in the main ‘app’ component’s View we’ll add the router links and router outlet.
<ul>
<li><a routerLink="/">Home</a></li>
<li><a routerLink="/user">User</a></li>
</ul>
<hr>
<router-outlet></router-outlet>
That’s all we need to get started with the lesson.
Types of Route Guards
Angular gives us multiple guards that we can use to protect our routes.
- CanActivate decides if a route (or component) can be activated, like a login system.
- CanDeactivate decides if a user can navigate away from a route (or component), like asking for confirmation of pending changes.
- Resolve will delay the activation of a route (or component) until certain tasks are complete, like fetching data from an external source.
- CanActivateChild is similar to CanActivate but applies to nested routes.
How to create a CanActivate Route Guard
A guard is just an Angular service that implements one of the interfaces we mentioned above. However, the CLI does provide us with a guard generation command that makes this setup a lot easier.
To create a new guard, we use the guard keyword with the standard generation command.
ng generate guard custom_guard_name
1. Let’s create a guard for our ‘user’ component that blocks the component from being loaded if a user is not logged in.
ng generate guard user
The CLI will ask which interfaces we would like to implement. To start with we will choose CanActivate , which should be selected by default so just press Enter.
If you want to select a different option you can use the arrow keys to navigate to it, and space to select it. Then press Enter to confirm the selection.
2. Let’s open the new user.guard.ts file and see what’s inside.
import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree } from '@angular/router';
import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class UserGuard implements CanActivate {
canActivate(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
return true;
}
}
As mentioned before, a guard is just a service that implements one of the guard interfaces that Angular makes available to us. These interfaces are provided in the ‘@angular/router’ package so it needs to be imported.
It also forces us to have a canActivate() method in the class, or a method with the same name as the guard we’re implementing.
The method uses the current route and router state and will return a boolean, or a boolean from an observable or promise, so all of those need to be imported as well.
If the method returns false, the component it guards cannot be accessed.
While the guard can be created manually, we recommend using the CLI to generate it. There’s a lot going on and the CLI will eliminate the possibility of any mistakes.
3. Next, we’ll inject the Router to be able to programmatically redirect the user back to the ‘home’ component if the login fails.
import { Injectable } from '@angular/core';
import { Router, CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree } from '@angular/router';
import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class UserGuard implements CanActivate {
constructor(private r: Router) { }
canActivate(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
return true;
}
}
4. Next, we need to import and register the guard in the providers array of the NgModule in the app.module.ts file.
...
import { UserGuard } from './user.guard';
import { Routes, RouterModule } from '@angular/router';
const appRoutes: Routes = [
{ path: '', component: HomeComponent },
{ path: 'user', component: UserComponent }
];
@NgModule({
declarations: [
AppComponent,
HomeComponent,
UserComponent
],
imports: [
BrowserModule,
RouterModule.forRoot(appRoutes)
],
providers: [UserGuard],
bootstrap: [AppComponent]
})
...
5. We also have to tell Angular which route we want to be guarded.
To do this we specify the guard we want to use in the canActivate array, or the array that matches the guard we’re using, in the routes.
{ path: '/path/to/component',
component: component_name,
canActivate: [guard_name]
}
In our case we want the ‘/user’ route to be guarded by the ‘UserGuard’.
...
const appRoutes: Routes = [
{ path: '', component: HomeComponent },
{ path: 'user', component: UserComponent, canActivate: [UserGuard] }
];
...
At the moment, if we try to visit the ‘User’ page in the browser, it will let us through because the method in the guard returns true.
6. Now, let’s add some fake login logic to the guard.
To keep things simple we’ll just let the method return false, alert a message and redirect the user back to the home page.
import { Injectable } from '@angular/core';
import { Router, CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree } from '@angular/router';
import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class UserGuard implements CanActivate {
constructor(private r: Router) { }
canActivate(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
alert('You are not allowed to view this page.');
this.r.navigate(['']);
return false;
}
}
The example above will not allow us to access the ‘user’ component because the method returns false.
The alert and redirection is just for better app usability.
Guard logic is typically imported from other services, like an authentication service that reaches out to an external source, and just returns true or false based on the imported logic.
How to create a CanDeactivate Route Guard
The CanDeactivate route guard allows us to do something before the user leaves a component.
For example, a user could edit their personal details but forget to save before navigating to another page. We can then use the CanDeactivate guard to prompt them to save before exiting.
1. Let’s create a CanDeactivate guard called ‘user-exit’ with the CLI.
ng generate guard user-exit
The CLI will prompt you to choose which type of guard you want to use. Choose the canDeactivate option and press Enter.
2. Let’s open the new user-exit.guard.ts file and see what it looks like.
import { Injectable } from '@angular/core';
import { CanDeactivate, ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree } from '@angular/router';
import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class UserExitGuard implements CanDeactivate<unknown> {
canDeactivate(
component: unknown,
currentRoute: ActivatedRouteSnapshot,
currentState: RouterStateSnapshot,
nextState?: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
return false;
}
}
Aside from two new things, it’s the same as the canActivate guard. We’ll look at those in a little bit.
3. We need to create some logic for the guard.
For this demonstration we want to simulate that our user wants to navigate away from the ‘user’ component but has forgotten to save some imaginary options. They will need to explicitly confirm that they want to exit without saving.
We’ll have a simple method that gives the user a confirmation popup and returns true or false depending on the choice.
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-user',
templateUrl: './user.component.html',
styleUrls: ['./user.component.css']
})
export class UserComponent implements OnInit {
constructor() { }
ngOnInit(): void {
}
canExit(): boolean {
if(confirm("Do you wish to exit without saving?")) {
return true;
} else {
return false;
}
}
}
As mentioned before, the method shows a popup and will return true or false depending on their choice.
4. Now we need to use the blocking logic in the guard.
The logic is in the ‘user’ component, so remember to import it.
In the canDeactivate() method there’s a new parameter called ‘component’. This object wants the name of the component our logic is in as the type, which in our case is the UserComponent.
Now we will have access to the canExit() method through the component object.
Because the canDeactivate() method also returns a true or false value, we can simply return the result of the canExit() blocking method.
import { Injectable } from '@angular/core';
import { CanDeactivate, ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree } from '@angular/router';
import { Observable } from 'rxjs';
import { UserComponent } from './user/user.component';
@Injectable({
providedIn: 'root'
})
export class UserExitGuard implements CanDeactivate<unknown> {
canDeactivate(
component: UserComponent,
currentRoute: ActivatedRouteSnapshot,
currentState: RouterStateSnapshot,
nextState?: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
return component.canExit();
}
}
5. All that’s left to do is to tell Angular which route we want the guard to run on.
We can just add the canDeactivate array in our routes in the app.module.ts file with the new guard as a value.
...
import { UserExitGuard } from './user-exit.guard';
const appRoutes: Routes = [
{ path: '', component: HomeComponent },
{ path: 'user', component: UserComponent, canDeactivate: [UserExitGuard] }
];
...
Now let’s run the app in the browser and navigate to the ‘User’ page. If we try to navigate away from it, to the ‘Home’ page for example, it will show us the confirmation popup.
We can’t navigate away from this component until we confirm the popup so our blocking guard works.
How to create a Resolver
We can use a resolver to pre-fetch data before a component is loaded.
For example, let’s say we have a component that shows all the daily sales for a specific month from an external resource.
If we load the component before the data is ready, the user will just see an empty component until the data is displayed.
We can use a resolver to load the data before the component.
Note that many apps prefer not to use resolvers, instead displaying the component with a loading spinner in the sections where data is still loading. From a UX point of view, this approach is more desirable and resolvers should not be used.
To keep things simple we will fake the data and delay in our resolver.
1. Let’s start by creating a resolver called ‘user’ with the CLI.
Even though a resolver technically falls under the Guard category, it uses the generate resolver command.
ng generate resolver user
2. Let’s open up the new user.resolver.ts file and see what it looks like.
import { Injectable } from '@angular/core';
import { Resolve, RouterStateSnapshot, ActivatedRouteSnapshot } from '@angular/router';
import { Observable, of } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class UserResolver implements Resolve<boolean> {
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> {
return of(true);
}
}
The class implements the Resolve interface and requires the resolve() method, which returns an observable of whatever type.
3. Next, we’ll create the resolver logic that will load before the component is loaded.
As mentioned before, we’re going to use a simple example. We will just send a short string message before the component is loaded and fake a delay that would normally be caused by reaching out to an external resource.
We will return a string message ‘Hello there!’ with a delay of 2 seconds.
Because we use the delay() method, it must be imported from ‘rxjs/internal/operators’ first.
Also, because we return a string, we have to change the interface and method return type from boolean to string.
import { Injectable } from '@angular/core';
import { Resolve, RouterStateSnapshot, ActivatedRouteSnapshot } from '@angular/router';
import { Observable, of } from 'rxjs';
import { delay } from 'rxjs/internal/operators';
@Injectable({
providedIn: 'root'
})
export class UserResolver implements Resolve<string> {
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<string> {
return of('Hello there!').pipe(delay(2000));
}
}
Our fake data will only be ready after 2 seconds, so the resolver will not load the component until then.
4. Next, let’s import and register our resolver in the providers array of the NgModule in the app.module.ts file.
...
import { UserResolver } from './user.resolver';
@NgModule({
declarations: [
AppComponent,
HomeComponent,
UserComponent
],
imports: [
BrowserModule,
RouterModule.forRoot(appRoutes)
],
providers: [UserResolver],
bootstrap: [AppComponent]
})
...
5. Next, we need to tell Angular which route the resolver will be working on in our routes. In our case our routes are defined in the app.module.ts file.
We’ll store the data that will be returned by UserResolver into the message property.
...
const appRoutes: Routes = [
{ path: '', component: HomeComponent },
{ path: 'user', component: UserComponent, resolve: { message: UserResolver } }
];
...
6. Finally, let’s output the data that comes from the resolver to the ‘user’ component’s view.
<p>Data: {{ data.message }}</p>
If we run the app and navigate to the User component, it will show the message we defined in the resolver after 2 seconds.