CanActivateChild Guard In Angular

CanActivateChild Guard In Angular

Before we navigate to a child route, the Angular CanActivateChild guard executes. In this article, we will learn what the CanActivateChild guard is and how to use it to protect kid routes. To demonstrate how to use Angular CanActivateChild in a real application, we'll develop a small Angular CanActivateChild sample app.

What is CanActivateChild Guard

CanActivateGuard and CanActivateChild are extremely similar guards. This guard is applied to the parent route. When a user navigates to one of Angular's child routes, this guard is triggered. This allows us to check for certain conditions before deciding whether or not to continue with the navigation.

Difference between CanActivate & CanActivateChild

Consider the options below.

The ProductComponent displays a product list. The canActivate guard has been included in the product route. If the user is not logged in, the canActivate guard prevents access to the route. Both the product route and all of its children are protected by this guard.

{ path: 'product', component: ProductComponent, canActivate : [AuthGuardService],
      children: [
      {  path: 'view/:id', component: ProductViewComponent  },
      {  path: 'edit/:id', component: ProductEditComponent  },
      {  path: 'add', component: ProductAddComponent }
      ]  
},

Consider the following scenario: we want all users to be able to see the ProductComponent, but only the Admin user can see any of its child routes.

We can make a new guard. As shown below, create a ProductGuardService that implements the canActivate guard and attach it to each of the child routes.

{ path: 'product', component: ProductComponent, canActivate : [AuthGuardService],
      children: [
      {  path: 'view/:id', component: ProductViewComponent, canActivate : [ProductGuardService]  },
      {  path: 'edit/:id', component: ProductEditComponent, canActivate : [ProductGuardService]  },
      {  path: 'add', component: ProductAddComponent, canActivate : [ProductGuardService] }
      ]  
},

Another option is to link the CanActivateChild guard to the product route, as seen below. When Angular detects a canActivateChild guard on the parent route, it calls it whenever the user attempts to browse to the child route. As a result, rather than applying Guard to each child, you may attach it to the parent route.

{ path: 'product', component: ProductComponent, canActivate : [AuthGuardService], canActivateChild : [AdminGuardService],
      children: [
      {  path: 'view/:id', component: ProductViewComponent  },
      {  path: 'edit/:id', component: ProductEditComponent  },
      {  path: 'add', component: ProductAddComponent }
      ]  
},

How to Create CanActivateChild Guard

We will need to establish an Angular Service, just like the rest of the Angular Guards. The CanActivateChild Interface must be imported and implemented by the service. The @angular/router module defines the interface. canActivateChild is the only method in the Interface. The CanActivateChild interface is described in detail below.

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

The method returns the ActivatedRouteSnapshot and RouterStateSnapshot instances. This can be used to access the route parameter, query parameter, and so on.

True/false or an UrlTree must be returned by the guard. These values can be returned as an observable, a promise, or a plain Boolean value.

Navigation will continue if all guards return true.

Navigation will be canceled if any guard returns false.

If a guard returns an UrlTree, the current navigation will be terminated and new navigation to the UrlTree returned by the guard will begin.

The following is an example of a canActivateChild guard.

import { Injectable } from '@angular/core';
import { Router, CanActivate, ActivatedRouteSnapshot,RouterStateSnapshot } from '@angular/router';
 
 
@Injectable()
export class AuthGuardService implements CanActivateChild {
 
    constructor(private _router:Router ) {
    }
 
    canActivate(route: ActivatedRouteSnapshot,
                state: RouterStateSnapshot): boolean {
 
        //check some condition  
        if (someCondition)  {
            alert('You are not allowed to view this page');
            //redirect to login/home page etc
            //return false to cancel the navigation
            return false;
        } 
        return true;
    }
 
}

Angular CanActivateChild Example

The HomeComponent and ContactComponent in our sample application are not protected and can be accessed by any user.

To access the ProductComponent, the user must first log into the system.

Child components of the ProductComponent are the ProductEditComponent and ProductViewComponets.

To manage user login, we will additionally require a LoginComponent.

Add the following code under the login.component.ts:

import { Component, OnInit } from '@angular/core';
import { FormControl, FormGroup } from '@angular/forms';
import { Router, ActivatedRoute } from '@angular/router';
import { AuthService } from './auth.service';
 
@Component({
   templateUrl: './login.component.html',
   styles: [``]
})
export class LoginComponent implements OnInit { 
 
    invalidCredentialMsg: string;
    username:string;
    password:string;
    retUrl:string="home";
 
    constructor(private authService: AuthService, 
                private router: Router, 
                private activatedRoute:ActivatedRoute) {
    }
 
    ngOnInit() {
        this.activatedRoute.queryParamMap
                .subscribe(params => {
            this.retUrl = params.get('retUrl'); 
            console.log( 'LoginComponent/ngOnInit '+ this.retUrl);
        });
    }
 
    onFormSubmit(loginForm) {
       this.authService.login(loginForm.value.username, loginForm.value.password).subscribe(data => {
           console.log( 'return to '+ this.retUrl);
           if (this.retUrl!=null) {
                this.router.navigate( [this.retUrl]);
           } else {
                this.router.navigate( ['home']);
           }
       });
    }
} 

Add the following code under the login.component.html:

<h3>Login Form</h3>
 
<div>
 <form #loginForm="ngForm" (ngSubmit)="onFormSubmit(loginForm)">
   <p>User Name: <input type='text'  name='username' ngModel></p>
   <p>Password: <input type="password"  name="password" ngModel></p>
   <p><button type="submit">Submit</button></p> 
 </form>
</div> 

Add the following code under the auth.service.ts:

import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/of';
import 'rxjs/add/operator/map';
import { of } from 'rxjs';
 
 
@Injectable()
export class AuthService { 
 
    private isloggedIn: boolean;
    private userName:string;
 
    constructor() {
        this.isloggedIn=false;
    }
 
    login(username: string, password:string) {
 
        //Assuming users are provided the correct credentials.
        //In real app you will query the database to verify.
        this.isloggedIn=true;
        this.userName=username;
        return of(this.isloggedIn);
    }
 
    isUserLoggedIn(): boolean {
        return this.isloggedIn;
    }
 
    isAdminUser():boolean {
        if (this.userName=='Admin') {
            return true; 
        }
        return false;
    }
    
    logoutUser(): void{
        this.isloggedIn = false;
    }
 
} 

The AuthService determines whether or not the user is permitted to log in. It has a technique for logging in and out users. The login procedure in our implementation does not perform any checks. It just indicates that the user is logged in. If the user is logged in with the user name "Admin", the isAdminUser() method returns true.

Product Component

Our protected component is the ProductComponent. This is only accessible to logged-in users. The ProductService returns a list of Products to this component, which it displays.

Add the following code under the product.component.ts:

import { Component, OnInit } from '@angular/core';
import { ProductService } from './product.service';
import { Product } from './Product';
 
 
@Component({
  templateUrl: "product.component.html",
})
export class ProductComponent
{
 
   products:Product[];
   constructor(private productService: ProductService){
   }
 
   ngOnInit() {
 
      this.productService.getProducts()
        .subscribe(data => {
          this.products=data;
        })
   }
  
}

Add the following code under the product.component.html:

<h1>Product List</h1>
   <p> This is a protected component </p>
 
   <div class='table-responsive'>
   <table class='table'>
       <thead>
           <tr>
               <th>Name</th>
               <th>Price</th>
               <th></th>
               <th></th>
           </tr>
       </thead>
       <tbody>
           <tr *ngFor="let product of products;">
               <td><a [routerLink]="['/product/view',product.productID]">{{product.name}}</a></td>
               <td>{{product.price}}</td>
               <td><a [routerLink]="['/product/view',product.productID]">View</a></td>
               <td><a [routerLink]="['/product/edit',product.productID]">Edit</a></td>
           </tr>
       </tbody>
     </table>
  </div>
 
  <router-outlet></router-outlet>

Add the following code under the product.service.ts:

import {Product} from './Product'
import { of, Observable, throwError} from 'rxjs';
import { delay, map } from 'rxjs/internal/operators';
 
export class ProductService{
 
    products: Product[];
    
    public constructor() {
        this.products=[
            new Product(1,'Memory Card',500),
            new Product(2,'Pen Drive',750),
            new Product(3,'Power Bank',100),
            new Product(4,'Computer',100),
            new Product(5,'Laptop',100),
            new Product(6,'Printer',100),
        ]
    }
 
    public getProducts(): Observable<Product[]> {
        return of(this.products) ;
    }
 
    public getProduct(id): Observable<Product> {
        var Product= this.products.find(i => i.productID==id)
        return of(Product);
    }
 
}

Add the following code under the product.ts:

export class Product { 
 
    constructor(productID:number,    name: string ,   price:number) {
        this.productID=productID;
        this.name=name;
        this.price=price;
    }
 
    productID:number ;
    name: string ;
    price:number;
 
}

We have components for adding, editing, and viewing products.

Add the following code under the product-add.component.ts:

import { Component, OnInit } from '@angular/core';
import { Product } from './Product';
import { ProductService } from './product.service';
import { ActivatedRoute } from '@angular/router';
 
 
 
@Component({
  template: `<h1>Add Product</h1>`,
})
export class ProductAddComponent
{
 
  product:Product;
 
  constructor(private productService: ProductService, 
              private route:ActivatedRoute ){
  }
 
 
  ngOnInit() {
  }
  
}

Add the following code under the product-edit.component.ts:

import { Component, OnInit } from '@angular/core';
import { Product } from './Product';
import { ProductService } from './product.service';
import { ActivatedRoute } from '@angular/router';
 
 
@Component({
  template: `<h1>Edit Product</h1>`,
})
export class ProductEditComponent
{
 
    product:Product
 
    constructor(private productService: ProductService, 
                private route:ActivatedRoute ){
    }
 
 
    ngOnInit() {
 
    let id=this.route.snapshot.params['id'];
 
    this.productService.getProduct(id)
        .subscribe(data => {
            this.product=data;
        })
    }
}

Add the following code under the product-view.component.ts:

import { Component, OnInit } from '@angular/core';
import { Product } from './Product';
import { ProductService } from './product.service';
import { ActivatedRoute } from '@angular/router';
 
 
@Component({
  template: `<h1>View Product</h1>`,
})
export class ProductViewComponent
{
 
    product:Product
 
    constructor(private productService: ProductService, 
                private route:ActivatedRoute ){
    }
 
 
    ngOnInit() {
 
        
        let id=this.route.snapshot.params['id'];
 
        this.productService.getProduct(id)
            .subscribe(data => {
                this.product=data;
            })
    }
 
}

Add the following code under the app.component.ts:

import { Component } from '@angular/core';
import { AuthService } from './auth.service';
import { Router } from '@angular/router';
 
@Component({
  selector: 'app-root',
  templateUrl: './app.component.html'
})
export class AppComponent {
  title = 'Routing Module - Route Guards Demo';
 
  constructor (private authService:AuthService, 
               private router:Router) {
  }
 
  logout() {
    this.authService.logoutUser();
    this.router.navigate(['home']);
  }
 
}

Add the following code under the app.component.html:

<div class="container">
 
<nav class="navbar navbar-default">
  <div class="container-fluid">
    <div class="navbar-header">
      <a class="navbar-brand" [routerLink]="['/']"><strong> {{title}} </strong></a>
    </div>
    <ul class="nav navbar-nav">
 
        <li><a [routerLink]="['home']">Home</a></li>
        <li><a [routerLink]="['product']">Product</a></li>
        <li><a [routerLink]="['contact']">Contact us</a></li>
        <li><a [routerLink]="['login']">Login</a></li>
        <li><a [routerLink]="" (click)="logout()">Log out</a></li>
        
    </ul>
  </div>
</nav>
 
 <router-outlet></router-outlet>
 
</div>

Add the following code under the home.component.ts:

import {Component} from '@angular/core';
 
@Component({
    template: `<h1>Welcome!</h1>
              <p>This is Home Component </p>
             `
})
 
export class HomeComponent {
}

contact.component.ts
import {Component} from '@angular/core';
 
@Component({
     template: `<h1>Contact Us</h1>
                <p>TekarticlesHub </p>
                `
})
export class ContactComponent {
}

CanActivateChild Guard

After that, we'll create the CanActivate guard, which will check whether or not the users are logged in. If the user is not logged in, they are redirected to the login page.

Also, the CanActivateChild guard, determines whether or not the user is an administrator. The ProductEditComponent and ProductViewComponent are only accessible to Admin Users.

Add the following code under the auth-guard.service.ts:

import { Injectable } from '@angular/core';
import { Router, CanActivate, ActivatedRouteSnapshot,RouterStateSnapshot, UrlTree, CanActivateChild } from '@angular/router';
import { AuthService } from './auth.service';
 
 
@Injectable()
export class AuthGuardService implements CanActivate , CanActivateChild {
 
    constructor(private router:Router, private authService: AuthService ) {
 
    }
 
    canActivate(route: ActivatedRouteSnapshot,
                state: RouterStateSnapshot): boolean|UrlTree {
 
        console.log('canActivate on '+route.url);
 
        if (!this.authService.isUserLoggedIn()) {
            alert('You are not allowed to view this page. You are redirected to login Page');
            this.router.navigate(["login"],{ queryParams: { retUrl: route.url } });
            return false;
 
            //var urlTree = this.router.createUrlTree(['login']);
            //return urlTree;
        } 
 
        return true;
    }
 
    canActivateChild(route: ActivatedRouteSnapshot,
                    state: RouterStateSnapshot): boolean|UrlTree {
 
 
        
        if (!this.authService.isAdminUser()) {
            alert('You are not allowed to view this page');
            return false;
        }
 
 
        return true;
}
 
}

First, we import the @angular/router module CanActivate and CanActivateChild functions.

The CanActivate and CanActivateChild interfaces are implemented by the AuthGuardService.

Inject the AuthServce into the Guard's constructor.

If the user is not logged in, the CanActivate method will redirect them to the login page. We must either return false or urlTree to abort the navigation, as seen in the example above.

While in the CanActivateChild method, we just check if the user is an administrator, and if so, we return true; otherwise, we return false.

After that, we'll edit the route definition and apply the guard to all of the routes we wish to protect.

Add the following code under the app.routes.ts:

import { Routes } from '@angular/router';
 
import { HomeComponent} from './home.component'
import { ContactComponent} from './contact.component'
import { ProductComponent } from './product.component'
 
import { AuthGuardService } from './auth-guard.service';
import { LoginComponent } from './login.component';
import { ProductViewComponent } from './product-view.component';
import { ProductAddComponent } from './product-add.component';
import { ProductEditComponent } from './product-edit.component';
 
 
export const appRoutes: Routes = [
  { path: 'home', component: HomeComponent },
  { path: 'login', component:LoginComponent},
  { path: 'contact', component: ContactComponent },
  { path: 'product', component: ProductComponent, canActivate : [AuthGuardService] ,
      canActivateChild : [AuthGuardService],
      children: [
      {  path: 'view/:id', component: ProductViewComponent  },
      {  path: 'edit/:id', component: ProductEditComponent  },
      {  path: 'add', component: ProductAddComponent }
      ]  
  },
 
  { path: '', redirectTo: 'home', pathMatch: 'full' },
];

Add the following code under the app.module.ts:

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { HttpModule } from '@angular/http';
import { FormsModule }    from '@angular/forms';
 
import { RouterModule } from '@angular/router';
 
import { AppComponent } from './app.component';
import { HomeComponent} from './home.component'
import { ContactComponent} from './contact.component'
import { ProductComponent} from './product.component'
 
import { AuthGuardService } from './auth-guard.service';
 
import { appRoutes } from './app.routes';
import { AuthService } from './auth.service';
import { LoginComponent } from './login.component';
import { ProductAddComponent } from './product-add.component';
import { ProductViewComponent } from './product-view.component';
import { ProductEditComponent } from './product-edit.component';
import { ProductService } from './product.service';
 
@NgModule({
  declarations: [
    AppComponent,HomeComponent,ContactComponent,ProductComponent,LoginComponent, ProductAddComponent, ProductViewComponent, ProductEditComponent
  ],
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule,
    RouterModule.forRoot(appRoutes)
  ],
  providers: [AuthGuardService,AuthService,ProductService],
  bootstrap: [AppComponent]
})
export class AppModule { }

On the product journey, we use both guards. The canActivate guards defend the product route, whereas the canActivateChild guards all of the route's children.

Run the application. Only by logging in as seen in the image below can you view the Product page.

Example of CanActivateChild Guard In Angular


Conclusion

In this article, we learned what the CanActivateChild guard is and how to use it to protect kid routes.

I hope this article helps you and you will like it.👍

If you have any doubt or confusion then free to ask in the comment section.

Post a Comment

Previous Post Next Post