Implementing CanActivate Guard in Angular

1. Introduction to Route Guards

In modern Angular applications, routing is not just about navigating between views. It’s also about controlling access to certain areas of your application based on conditions like user authentication, role-based access, or unsaved changes.

Angular provides route guards as a built-in mechanism to control navigation. They allow developers to determine whether a route can be activated, deactivated, loaded, or accessed by child routes.

Among the various types of guards (CanActivate, CanDeactivate, CanLoad, CanActivateChild, and Resolve), CanActivate is the most commonly used for restricting access to routes.

2. Understanding CanActivate Guard

The CanActivate interface defines a guard that determines if a route can be activated. When a user tries to navigate to a route, Angular calls the canActivate method of the guard. If it returns:

  • true → navigation proceeds
  • false → navigation is canceled

This guard is primarily used to:

  • Restrict access to routes based on authentication
  • Check for user roles or permissions
  • Prevent unauthorized users from accessing sensitive areas

3. Setting Up the Auth Service

Before implementing the guard, you need an authentication service to check whether a user is logged in.

Example AuthService

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

@Injectable({ providedIn: 'root' })
export class AuthService {
  private loggedIn = false;

  login() {
this.loggedIn = true;
} logout() {
this.loggedIn = false;
} isLoggedIn(): boolean {
return this.loggedIn;
} }

Here:

  • login() and logout() methods simulate authentication.
  • isLoggedIn() returns a boolean indicating the user’s login status.

4. Creating the CanActivate Guard

Use Angular CLI or manual creation to generate a guard:

ng generate guard auth

This creates a guard implementing the CanActivate interface.

AuthGuard Example

import { Injectable } from '@angular/core';
import { CanActivate, Router } from '@angular/router';
import { AuthService } from './auth.service';

@Injectable({ providedIn: 'root' })
export class AuthGuard implements CanActivate {
  constructor(private authService: AuthService, private router: Router) {}

  canActivate(): boolean {
if (this.authService.isLoggedIn()) {
  return true;
} else {
  this.router.navigate(['/login']);
  return false;
}
} }

Explanation

  • The guard injects AuthService to check login status.
  • It injects Router to redirect unauthorized users.
  • Returns true if the user is logged in, otherwise navigates to /login and returns false.

5. Applying the CanActivate Guard to Routes

Once the guard is created, you need to attach it to the route(s) you want to protect.

Example Route Setup

import { Routes } from '@angular/router';
import { DashboardComponent } from './dashboard/dashboard.component';
import { AuthGuard } from './auth.guard';
import { LoginComponent } from './login/login.component';

const routes: Routes = [
  { path: '', component: LoginComponent },
  { path: 'dashboard', component: DashboardComponent, canActivate: [AuthGuard] }
];

Here:

  • The DashboardComponent is protected.
  • Users who are not logged in are redirected to the login page.

6. Handling Asynchronous Authentication

In real applications, authentication checks are often asynchronous, e.g., verifying a token with a backend server. In such cases, canActivate can return:

  • Observable<boolean>
  • Promise<boolean>

Example with Observable

import { Injectable } from '@angular/core';
import { CanActivate, Router } from '@angular/router';
import { AuthService } from './auth.service';
import { Observable, of } from 'rxjs';
import { map } from 'rxjs/operators';

@Injectable({ providedIn: 'root' })
export class AuthGuard implements CanActivate {
  constructor(private authService: AuthService, private router: Router) {}

  canActivate(): Observable<boolean> {
return this.authService.checkToken().pipe(
  map(isValid =&gt; {
    if (isValid) {
      return true;
    } else {
      this.router.navigate(&#91;'/login']);
      return false;
    }
  })
);
} }

AuthService Example for Token Check

checkToken(): Observable<boolean> {
  // Simulate HTTP request to validate token
  return of(localStorage.getItem('token') ? true : false);
}

This approach ensures guards work even when validation requires asynchronous operations.


7. Using CanActivate with Multiple Guards

You can apply multiple guards to a single route. Angular evaluates them in the order specified. All guards must return true for navigation to proceed.

Example

{ 
  path: 'admin', 
  component: AdminComponent, 
  canActivate: [AuthGuard, RoleGuard] 
}

Here:

  • AuthGuard checks if the user is logged in.
  • RoleGuard checks if the user has the necessary role (like ‘admin’).

8. Role-Based Route Protection

Guards can enforce role-based access control (RBAC) by checking user roles.

RoleGuard Example

@Injectable({ providedIn: 'root' })
export class RoleGuard implements CanActivate {
  constructor(private authService: AuthService, private router: Router) {}

  canActivate(): boolean {
const userRole = this.authService.getUserRole(); // e.g., 'admin'
if (userRole === 'admin') {
  return true;
} else {
  this.router.navigate(&#91;'/unauthorized']);
  return false;
}
} }

Usage in Routes:

{ path: 'admin', component: AdminComponent, canActivate: [AuthGuard, RoleGuard] }

9. Redirecting Unauthorized Users

Instead of simply preventing access, it’s a good practice to redirect unauthorized users to a meaningful page, such as:

  • Login page (/login)
  • Unauthorized access page (/unauthorized)

This improves user experience and provides clear navigation feedback.


10. CanActivate Child Routes

CanActivateChild is useful when you want to guard all child routes of a parent module.

Example

{ 
  path: 'admin', 
  component: AdminComponent,
  canActivateChild: [AuthGuard],
  children: [
{ path: 'users', component: UsersComponent },
{ path: 'settings', component: SettingsComponent }
] }

Here, the guard runs whenever a user navigates to /admin/users or /admin/settings.


11. Best Practices for CanActivate Guards

  1. Keep guards simple: Only include logic related to navigation.
  2. Return Observable or Promise when dealing with async checks.
  3. Avoid side effects in guards; use services for data operations.
  4. Combine with other guards for layered security (e.g., Auth + Role).
  5. Use redirect routes to improve UX for unauthorized access.
  6. Centralize authentication logic in a service to avoid duplication.
  7. Test guards to ensure they prevent unauthorized access correctly.

12. Testing CanActivate Guards

Testing guards ensures your route protection works as expected.

Example Unit Test

import { TestBed } from '@angular/core/testing';
import { Router } from '@angular/router';
import { AuthGuard } from './auth.guard';
import { AuthService } from './auth.service';

describe('AuthGuard', () => {
  let guard: AuthGuard;
  let authService: AuthService;
  let routerSpy = { navigate: jasmine.createSpy('navigate') };

  beforeEach(() => {
TestBed.configureTestingModule({
  providers: &#91;
    AuthGuard,
    { provide: AuthService, useValue: { isLoggedIn: () =&gt; false } },
    { provide: Router, useValue: routerSpy }
  ]
});
guard = TestBed.inject(AuthGuard);
authService = TestBed.inject(AuthService);
}); it('should redirect unauthorized users', () => {
expect(guard.canActivate()).toBe(false);
expect(routerSpy.navigate).toHaveBeenCalledWith(&#91;'/login']);
}); });

13. Using CanActivate with Lazy-Loaded Modules

Guards also protect lazy-loaded modules using the canLoad interface. However, CanActivate still works for routes within the lazy-loaded module.

Example

{ 
  path: 'admin', 
  loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule),
  canActivate: [AuthGuard]
}

Navigation to /admin will trigger the AuthGuard.


14. Handling Observables in Guards

For guards that rely on backend checks, you can use HTTP requests combined with RxJS operators.

Example

canActivate(): Observable<boolean> {
  return this.authService.validateToken().pipe(
map(response =&gt; response.isValid),
catchError(() =&gt; {
  this.router.navigate(&#91;'/login']);
  return of(false);
})
); }

This allows you to handle asynchronous authentication gracefully.


15. Summary

  • CanActivate guards control whether a route can be accessed.
  • Useful for authentication, role-based access, and secure navigation.
  • Guards can return boolean, Observable<boolean>, or Promise<boolean>.
  • Combine multiple guards for layered security.
  • Use Router to redirect unauthorized users.
  • Guards can work with lazy-loaded modules and child routes.
  • Keep guard logic simple, testable, and centralized in services.

Proper implementation of CanActivate guards ensures that your Angular application is secure, user-friendly, and maintainable.


16. Complete Example

AuthService

@Injectable({ providedIn: 'root' })
export class AuthService {
  private loggedIn = false;
  login() { this.loggedIn = true; }
  logout() { this.loggedIn = false; }
  isLoggedIn(): boolean { return this.loggedIn; }
}

AuthGuard

@Injectable({ providedIn: 'root' })
export class AuthGuard implements CanActivate {
  constructor(private auth: AuthService, private router: Router) {}

  canActivate(): boolean {
if (this.auth.isLoggedIn()) return true;
this.router.navigate(&#91;'/login']);
return false;
} }

Routes

const routes: Routes = [
  { path: '', component: LoginComponent },
  { path: 'dashboard', component: DashboardComponent, canActivate: [AuthGuard] }
];

This configuration provides basic authentication-based route protection in Angular.


Comments

Leave a Reply

Your email address will not be published. Required fields are marked *