Tuesday, August 25, 2020

Angular CanActivateChild Guard to protect Child Routes

In the post Angular Access Control CanActivate Route Guard Example we saw an example of canActivate guard and how it protects a route including its child routes. You can also protect child routes with the CanActivateChild guard in Angular. If you want to guard only the child component then you can use the CanActivateChild guard which is similar to the CanActivate guard with the difference that it runs before any child route is activated.

CanActivateChild interface in Angular

A class implementing CanActivateChild interface can act as a guard deciding if a child route can be activated. If all guards return true, navigation continues. If any guard returns false, navigation is cancelled.

There is one method canActivateChild in the interface.

interface CanActivateChild {
  canActivateChild(childRoute: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree
}

CanActivateChild route guard to protect nested routes Angular example

In the example we have an AccountsComponent that shows the account numbers and a AccountComponent that displays the details for the selected account. AccountComponent is configured as the child component of AccountsComponent. We want to ensure that the account details can only be accessed when the user is logged in. This is done by configuring CanActivateChild route guard.

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 and CanActivateChild 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. Since the parameters are same and the required functionality is also same (checking if user is logged in or not) so from canActivateChild() method you can invoke canActivate method.

import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree, Router, CanActivateChild } from '@angular/router';
import { Injectable } from '@angular/core';
import { AuthService } from './services/auth.service';
import { Observable } from 'rxjs';

@Injectable()
export class AuthGuard implements CanActivate, CanActivateChild{
  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']);
    }
  }

  canActivateChild(childRoute: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<boolean> | Observable<boolean> | boolean {
    return this.canActivate(childRoute, state);
  }
}

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)

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)

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)

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 canActivateChild guard class specified. This route guard will work on the child route AccountComponent.

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 { ServiceComponent } from './service.component';
import { AuthGuard } from './authguard.service';
import { LoginComponent } from './login.component';

const routes: Routes = [
                {path: 'home', component: HomeComponent},  
                {path: 'login', component: LoginComponent},                
                {path: 'account', canActivateChild: [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, that will be accessible but trying to access account details will navigate you back to home page because of the canActivateChild() method implementation.

That's all for this topic Angular CanActivateChild Guard to protect Child Routes. If you have any doubt or any suggestions to make please drop a comment. Thanks!

>>>Return to Angular Tutorial Page


Related Topics

  1. Nested Route (Child Route) in Angular
  2. Setting Wild Card Route in Angular
  3. Navigate to a Route Programmatically in Angular
  4. Setting and Fetching Route Parameters in Angular
  5. Highlight Currently Selected Menu Item Angular Routing Example

You may also like-

  1. Angular Disable Button Example
  2. Service in Angular With Examples
  3. Angular Application Bootstrap Process
  4. Angular Attribute Binding With Examples
  5. How to Sort ArrayList in Java
  6. Java Collections Interview Questions And Answers
  7. Check String Null or Empty in Java
  8. Installing Anaconda Distribution On Windows

No comments:

Post a Comment