Class based Route Guards

Route guards in Angular are like gatekeepers that control who can access which routes in an application. They intercept navigation attempts and execute specific logic before allowing or denying access to the desired route. This makes it possible to implement authentication, authorization, and other access control mechanisms in the application.

Types of route guards:

1)CanActivate

The CanActivate route guard is used to protect routes from being accessed based on the condition. It returns a boolean value or an observable/promise that resolves to a boolean, indicating whether the user can activate the route.

AuthGuard Service

//auth-guard.service.ts
import { CanActivate, ActivatedRoute, RouterState } from '@angular/router';

export class AuthGuard implements CanActivate {

  canActivate(activatedRoute: ActivatedRoute, routerState: RouterState): boolean {
    // Check if the user is authenticated.
    if (localStorage.getItem('isAuthenticated') === 'true') {
      return true;
    }

    // redirect to the login page if user is not authenticated.

    return false;
  }

As we have implemented the AuthGuard above now we can protect the required route by using the guard as shown below.

Note: The AuthGuard should be added as a Provider in the module.

Route configuration

const routes: Routes = [
  { path: '', redirectTo: '/users', pathMatch: 'full' },
  {
    path: 'users',
    component: UsersComponent,
    canActivate: [AuthGuard],
    children: [
      { path: 'new', component: EditUserComponent },
      { path: ':id', component: UserDetailComponent },
      { path: ':id/edit', component: RecipeUserComponent },
    ],
  },
  { path: 'orders', component: OrdersComponent },
];

In the above example, only the parent route will be protected as we have added only 'canActivate'.

To protect the children routes we need to use 'CanActivateChild'.

2)CanActivateChild

It is similar to CanActivate but focuses on protecting the child routes of a route. It is useful when you want to apply authorization logic to all child routes of a specific parent route.

To protect both the parent and child routes with the same guard modify the AuthGuard and routing module as below.

AuthGard

//auth-guard.service.ts
import { CanActivate, CanActivateChild, ActivatedRoute, RouterState } from '@angular/router';

export class AuthGuard implements CanActivate, CanActivateChild {

  canActivate(activatedRoute: ActivatedRoute, routerState: RouterState): boolean {
    // Check if the user is authenticated.
    if (localStorage.getItem('isAuthenticated') === 'true') {
      return true;
    }

    // redirect to the login page if user is not authenticated.

    return false;
  }

   canActivate(activatedRoute: ActivatedRoute, routerState: RouterState): boolean {
    return this.canActivate(activatedRoute, routerState);
   }

Route configuration

const routes: Routes = [
  { path: '', redirectTo: '/users', pathMatch: 'full' },
  {
    path: 'users',
    component: UsersComponent,
    canActivateChild: [AuthGuard],
    children: [
      { path: 'new', component: EditUserComponent },
      { path: ':id', component: UserDetailComponent },
      { path: ':id/edit', component: RecipeUserComponent },
    ],
  },
  { path: 'orders', component: OrdersComponent },
];

3)CanDeactivate

It allows to prompt the user for confirmation before leaving a route. It is helpful when you want to prevent users from losing unsaved changes or data.

CanDeactivate guard

import { CanDeactivate, ActivatedRoute, RouterState } from '@angular/router';

export interface canComponentDeactivate{
 canDeactivate: () => Observable<boolean> | Promise<boolean> | boolean;
}

export class CanDeactivateGuard implements CanDeactivate<canComponentDeactivate> {

  canDeactivate(
    component: canComponentDeactivate,
    activatedRoute: ActivatedRouteSnapshot,
    routerState: RouterStateSnapshot,
    nextState?: RouterStateSnapshot,
  ): Observable<boolean> | Promise<boolean> | boolean {
    return component.CanDeactivate();
  }
}

Component

@Component({
  selector: 'app-form',
  template: `
    <h2>Form</h2>
    <input [(ngModel)]="formData" />
    <button (click)="onSave()">Save</button>
  `,
})
export class FormComponent implements canComponentDeactivate{
  formData: string = '';
  isDataSaved: boolean = false;

  onSave() {
    // Simulate saving data to backend
    this.isDataSaved = true;
  }

  canDeactivate(){
    if(this.formData.length>0 && !this.isDataSaved){
       return Confirm("You have unsaved changes. Are you sure you want to leave?")
    }
    return true;
  }
}

Route configuration

{ path: ':id/edit', 
  component: RecipeUserComponent 
  canDeactivate: [CanDeactivateGuard]
}

4)Resolve

It allows fetching data before a route is activated. By using the Resolve guard, you can ensure that the data is available when the component is first loaded, which improves the user experience.

In the below example, a list of items is fetched using the OrdersResolveGuard before the OrdersComponent is activated. Then, the OrdersComponent subscribes to the ActivatedRoute's data property, which is where the resolved data is available. This ensures that the products are available when the component is first loaded.

Service

import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class OrdersService {
  private orders: any = [{id:1,status:'delivered'}, {id:2,status:'delivered'}, {id:3,status:'ordered'}];

  getOrders(): Promise<any> {
    // async op
    return new Promise(resolve => {
      setTimeout(() => resolve(this.orders), 3000);
    });
  }
}

Component

import { Component } from '@angular/core';
import { ActivatedRoute } from '@angular/router';

@Component({
  selector: 'app-orders',
  template: `
    <h2>Orders</h2>
    <ul>
      <li *ngFor="let order of orders">{{ user }}</li>
    </ul>
  `,
})
export class OrdersComponent {
  orders: any = [];

  constructor(private route: ActivatedRoute) {
   //resolved data is available in route's data
    this.route.data.subscribe(data => {
      this.orders= data.orders;
    });
  }
}

Resolve guard

import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
import { OrdersService } from './orders.service';

@Injectable({
  providedIn: 'root'
})
export class OrdersResolver implements Resolve<any> {

  constructor(private ordersService: OrdersService) {}

  resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<any> {
    return this.ordersService.getOrders();
  }
}

Route configuration

const routes: Routes = [
  {
    path: 'orders',
    component: OrdersComponent,
    resolve: {
      orders: OrdersResolver
    }
  },
];

Note:

Class-based route guards are deprecated in the latest version of Angular(v15 and above) and functional route guards are introduced.

Did you find this article valuable?

Support Sankarshan Ramesh by becoming a sponsor. Any amount is appreciated!