In your Angular application you may need to control access to different parts of your app. To control that kind of authorization and authentication you can use route guards in Angular. You can say that using route guards you can perform some action before navigating to another route. In this post we’ll see example of using CanActivate route guard in Angular for authentication.
Why route guards in Angular
Use of route guards to control access in your Angular app may be considered for following scenarios-
- User is not authorized to navigate to the component rendered by the route user want to navigate to.
- User is not authenticated (not logged in).
- You need to fetch some data before you display the target component.
- You want to save pending changes before leaving a component.
- You want to ask the user if it's OK to discard pending changes rather than save them.
You can add route guards to the route configuration to handle the above mentioned scenarios.
Value returned by route guard controls the router's behavior:
- If it returns true, the navigation process continues.
- If it returns false, the navigation process stops and the user may remain at the same component or navigated to another route.
- If it returns a UrlTree, the current navigation cancels and a new navigation is initiated to the UrlTree returned.
Route guard interfaces in Angular
In Angular there are multiple guard interfaces:
- CanActivate- to control navigation to a route.
- CanActivateChild- to control navigation to a child route. Refer Angular CanActivateChild Guard to protect Child Routes to know more about CanActivateChild.
- CanDeactivate- to control navigation away from the current route by asking user if it is ok to navigate away. Refer CanDeactivate Guard in Angular With Example to know more about CanDeactivate.
- Resolve- to perform route data retrieval before route activation.
- CanLoad- to mediate navigation to a feature module loaded asynchronously.
CanActivate interface in Angular
A class implementing CanActivate interface can act as a guard deciding if a route can be activated. If all guards return true, navigation continues. If any guard returns false, navigation is cancelled. If any guard returns a UrlTree, the current navigation is cancelled and a new navigation begins to the UrlTree returned from the guard.
There is one method in CanActivate interface
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree
A routing guard can return an Observable<boolean> or a Promise<boolean> asynchronously or a boolean synchronously.
How to use CanActivate routeguard
First thing you need to do is to create a class that implements CanActivate interface. Add that guard to the route which has to be guarded. For example there is a class AuthGuard that implements CanActivate and using that you want to guard the route for account and its child routes.
{path: 'account', canActivate: [AuthGuard], component: AccountsComponent, children: [ {path: ':acctno', component: AccountComponent} ]},
As you can see canActivate property is used for adding guards which takes an array of guards that has to be applied on a specific route.
Authentication using CanActivate routeguard Angular example
In the example we want to ensure that AccountComponent can only be accessed when the user is logged in.
First we’ll create a simple AuthenticationService responsible for authentication and authorization of routes. For simplicity here we’ll check for hardcoded values ‘admin’ and ‘angular’ as the correct values for user and password rather than fetching the values from persistence.
If value matches, username is also stored into localStorage for further use.
AuthService (auth.service.ts)
import { Injectable } from '@angular/core'; @Injectable({ providedIn: 'root' }) export class AuthService{ login(user: string, password: string): boolean{ if(user==='admin' && password==='angular'){ localStorage.setItem('user', user); return true; } return false; } logout(){ localStorage.removeItem('user'); } getUser(): string{ return localStorage.getItem('user'); } isLoggedIn(): boolean{ return localStorage.getItem('user') !== null; } }
AuthGuard (authguard.service.ts)
This is the guard class that implements CanActivate interface. In the canActivate() method you check if the user is already logged in or not, if yes then return true otherwise navigate to home page.
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree, Router } from '@angular/router'; import { Injectable } from '@angular/core'; import { AuthService } from './services/auth.service'; import { Observable } from 'rxjs'; @Injectable() export class AuthGuard implements CanActivate{ constructor(private authService: AuthService, private router: Router){} canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean { const isLoggedIn = this.authService.isLoggedIn(); if(isLoggedIn){ return true; }else{ this.router.navigate(['/home']); } } }
LoginComponent (login.component.ts)
In the component there is a login() method that calls the login method of the AuthService by passing the entered values of user and password fields. If there is no match then the error message is set otherwise navigate to home page.
import { Component } from '@angular/core'; import { AuthService } from './services/auth.service'; import { Router } from '@angular/router'; @Component({ selector: 'app-login', templateUrl: 'login.component.html' }) export class LoginComponent{ message: string; loggedIn = false; constructor(private authService: AuthService, private router: Router){ this.message = ''; this.loggedIn = this.authService.isLoggedIn(); } login(user: string, password: string){ if(!this.authService.login(user, password)){ this.message = 'User Name or Password is incorrect'; return false; }else{ this.router.navigate(['/home']); } } }
login.component.html
<form *ngIf="!loggedIn"> <div class="alert alert-danger" role="alert" *ngIf="message"> {{ message }} </div> <div class="form-group"> <label for="username">User:</label> <input class="form-control" name="username" placeholder="Enter user name" #username> </div> <div class="form-group"> <label for="password">Password:</label> <input class="form-control" type="password" name="password" placeholder="Enter password" #password> </div> <button type="submit" class="btn btn-primary" (click)="login(username.value, password.value)"> Login </button> </form>
HomeComponent (home.component.ts)
This is the component for showing home page where you have a login button.
import { OnInit, Component } from '@angular/core'; import { Router } from '@angular/router'; import { AuthService } from './services/auth.service'; @Component({ selector: 'app-home', templateUrl: './home.component.html' }) export class HomeComponent implements OnInit { loggedIn = false; user: string; constructor(private authService: AuthService, private router: Router) { this.loggedIn = this.authService.isLoggedIn(); this.user = this.authService.getUser(); } ngOnInit() { } onClickLogin(){ this.router.navigate(['/login']) } logout(){ this.authService.logout(); } }
home.component.html
<h4>Welcome to XYZ Bank</h4> <div *ngIf="!loggedIn"> <button class="btn btn-primary" (click)="onClickLogin()">Login</button> </div> <div *ngIf="loggedIn"> Logged in as <b>{{ user }}</b> <a href (click)="logout()"> Log out</a> </div>
AccountsComponent (accounts.component.ts)
This is the parent component which shows the account numbers.
import { Component, OnInit } from '@angular/core'; import { Router, ActivatedRoute } from '@angular/router'; @Component({ selector: 'app-accounts', templateUrl: './accounts.component.html' }) export class AccountsComponent implements OnInit{ accountnumbers = []; constructor(private router: Router, private route: ActivatedRoute) {} ngOnInit() { this.accountnumbers = ['A1001', 'A1002']; } }
accounts.component.html
<div class= "row"> <div class="col-xs-4 col-md-4"> <h2>Account Numbers</h2> <div class="list-group"> <a [routerLink]="['/account', accountnumber]" href="#" class="list-group-item" *ngFor="let accountnumber of accountnumbers"> {{ accountnumber }} </a> </div> </div> <div class="col-xs-4 col-md-4"> <router-outlet></router-outlet> </div> </div>
AccountComponent (account.component.ts)
This is the child component that shows the details for the selected account number.
import { Component, OnInit } from '@angular/core'; import { ActivatedRoute, Params, Router, RouterEvent } from '@angular/router'; @Component({ selector: 'app-account', templateUrl: './account.component.html' }) export class AccountComponent implements OnInit{ acctNo: string; account: {accountnumber: string, type: string, balance: number}; constructor(private route: ActivatedRoute, private router: Router){ } accountDetails = [ { accountnumber: 'A1001', type: 'Saving', balance: 22000 }, { accountnumber: 'A1002', type: 'Checking', balance: 1000 } ]; ngOnInit() { this.route.params.subscribe((params: Params)=> { this.acctNo = params['acctno']; this.account = this.accountDetails.find(e=>e.accountnumber === this.acctNo); }); } }
account.component.html
<h2>Account Details</h2> <div class="row"> <div class="col-xs-6"> <label>Account Number: </label> {{ account.accountnumber }} </div> </div> <div class="row"> <div class="col-xs-6"> <label>Account Type: </label> {{ account.type }} </div> </div> <div class="row"> <div class="col-xs-6"> <label>Balance: </label> {{account.balance}} </div> </div>
app-routing.module.ts
This is the routing module where we have the route definitions. With AccountsComponent route there is a canActivate guard class specified. This route guard will work on the child route AccountComponent too.
import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; import { AccountsComponent } from './accounts/accounts.component'; import { AccountComponent } from './accounts/account/account.component'; import { HomeComponent } from './home.component'; import { AuthGuard } from './authguard.service'; import { LoginComponent } from './login.component'; const routes: Routes = [ {path: 'home', component: HomeComponent}, {path: 'login', component: LoginComponent}, {path: 'account', canActivate: [AuthGuard], component: AccountsComponent, children: [ {path: ':acctno', component: AccountComponent} ]}, {path: '', redirectTo:'/home', pathMatch: 'full'} ]; @NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule] }) export class AppRoutingModule { }
app.component.html
This is the root template where menu options with route links are specified.
<nav class="navbar navbar-expand-md bg-dark navbar-dark"> <div class="container-fluid"> <div class="collapse navbar-collapse" id="collapsibleNavbar"> <ul class="nav navbar-nav"> <li class="nav-item" routerLinkActive="active"> <a class="nav-link" routerLink="/home">Home</a> </li> <li class="nav-item" routerLinkActive="active"> <a class="nav-link" routerLink="/account">Accounts</a> </li> </ul> </div> </div> </nav> <div class="container"> <div class="row"><p></p></div> <div class="row"> <div class="col-md-12"> <router-outlet></router-outlet> </div> </div> </div>
All the components (in decarations) and services (in providers) are to be added to the app module too.
providers: [AuthService, AuthGuard], imports: [ BrowserModule, AppRoutingModule ],
Once the configuration is done and code is successfully compiled access localhost:4200
If you try to access Account option without logging in you won’t be able to access it because of the canActivate() method implementation.
Login Page
Reference: https://angular.io/guide/router-tutorial-toh#milestone-5-route-guards
That's all for this topic Angular Access Control CanActivate Route Guard. If you have any doubt or any suggestions to make please drop a comment. Thanks!
>>>Return to Angular Tutorial Page
Related Topics
You may also like-
No comments:
Post a Comment