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 } ] },
{ 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
interface CanActivateChild { canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree }
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
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; } }
Product Component
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; }
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; }) } }
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
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; } }
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 { }