Route Guards
Understand how Angular route guards control route matching, activation, child-route access, and navigation away from components.
Lesson content
What are Route Guards? Route guards are functions that control whether Angular Router should allow, block, redirect, or skip a navigation. They are commonly used for authentication, authorization, feature flags, and unsaved-form protection. Route Guard Flow User requests a route ↓ Angular Router checks guards ↓ Guard returns a result ↓ Allow, Block, Redirect, or Try Another Route Types of Route Guards Guard Purpose canActivate Controls access to a route canActivateChild Controls access to child routes canDeactivate Controls whether a user can leave a route canMatch Controls whether a route configuration can match Guard Return Values A route guard can return: true – Allow navigation false – Block navigation UrlTree – Redirect to another URL RedirectCommand – Redirect with navigation options Promise – Resolve the guard asynchronously Observable – Emit the guard result asynchronously For an Observable or Promise, Angular uses the first resolved or emitted result. 1. canActivate The canActivate guard determines whether a user can enter a route. It is commonly used to protect authenticated pages. Authentication Guard import { inject } from '@angular/core'; import { CanActivateFn, Router } from '@angular/router'; export const authGuard: CanActivateFn = ( route, state ) => { const authService = inject(AuthService); const router = inject(Router); if (authService.isLoggedIn()) { return true; } return router.createUrlTree( ['/login'], { queryParams: { returnUrl: state.url } } ); }; Apply canActivate import { Routes } from '@angular/router'; export const routes: Routes = [ { path: 'dashboard', canActivate: [ authGuard ], loadComponent: () => import('./dashboard.component') .then( module => module.DashboardComponent ) } ]; Flow User opens /dashboard ↓ authGuard runs ↓ Is logged in? ┌────┴────┐ │ │ Yes No │ │ ▼ ▼ Dashboard Login Page 2. Role-Based Authorization Guard A guard can also check whether the current user has a required role or permission. import { inject } from '@angular/core'; import { CanActivateFn, Router } from '@angular/router'; export const adminGuard: CanActivateFn = ( route ) => { const authService = inject(AuthService); const router = inject(Router); const requiredRole = route.data['role'] as string; return authService.hasRole(requiredRole) ? true : router.createUrlTree( ['/forbidden'] ); }; Route Configuration { path: 'admin', data: { role: 'admin' }, canActivate: [ authGuard, adminGuard ], loadComponent: () => import('./admin.component') .then( module => module.AdminComponent ) } When multiple guards are added, Angular runs them in array order. 3. canActivateChild The canActivateChild guard protects child routes under a parent route. import { inject } from '@angular/core'; import { CanActivateChildFn, Router } from '@angular/router'; export const adminChildGuard: CanActivateChildFn = () => { const authService = inject(AuthService); const router = inject(Router); return authService.hasRole('admin') ? true : router.createUrlTree( ['/forbidden'] ); }; Apply canActivateChild export const routes: Routes = [ { path: 'admin', canActivateChild: [ adminChildGuard ], children: [ { path: 'users', loadComponent: () => import('./users.component') .then( module => module.UsersComponent ) }, { path: 'settings', loadComponent: () => import('./settings.component') .then( module => module.SettingsComponent ) } ] } ]; Flow /admin ├── /users └── /settings ↓ canActivateChild ↓ Protects all child routes 4. canDeactivate The canDeactivate guard determines whether the user can leave the current route. It is commonly used to warn users about unsaved form changes. Component Contract export interface PendingChanges { hasUnsavedChanges(): boolean; } Component Example import { Component } from '@angular/core'; @Component({ selector: 'app-profile-editor', standalone: true, template: ` <form> <input (input)="isDirty = true" /> </form> ` }) export class ProfileEditorComponent implements PendingChanges { isDirty = false; hasUnsavedChanges(): boolean { return this.isDirty; } } canDeactivate Guard import { CanDeactivateFn } from '@angular/router'; export const unsavedChangesGuard: CanDeactivateFn<PendingChanges> = component => { if (!component.hasUnsavedChanges()) { return true; } return window.confirm( 'You have unsaved changes. Leave this page?' ); }; Apply canDeactivate { path: 'profile/edit', canDeactivate: [ unsavedChangesGuard ], loadComponent: () => import('./profile-editor.component') .then( module => module.ProfileEditorComponent ) } 5. canMatch The canMatch guard determines whether Angular should use a route configuration while matching a URL. It is useful for: Feature flags A/B testing Role-based component selection Conditional route availability Feature Flag Guard import { inject } from '@angular/core'; import { CanMatchFn } from '@angular/router'; export const newDashboardGuard: CanMatchFn = () => { const featureService = inject(FeatureService); return featureService.isEnabled( 'newDashboard' ); }; Apply canMatch export const routes: Routes = [ { path: 'dashboard', canMatch: [ newDashboardGuard ], loadComponent: () => import('./new-dashboard.component') .then( module => module.NewDashboardComponent ) }, { path: 'dashboard', loadComponent: () => import('./old-dashboard.component') .then( module => module.OldDashboardComponent ) } ]; Important canMatch Behaviour When canMatch returns false , Angular does not completely cancel navigation. Instead, Angular skips that route configuration and tries another matching route. First /dashboard route ↓ canMatch returns false ↓ Route skipped ↓ Angular tries next /dashboard route canActivate vs canMatch canActivate canMatch Runs after a route has matched Controls whether a route can match false cancels navigation false tries another matching route Commonly protects route access Commonly used for feature flags and conditional routes Receives route and router state Receives route configuration and URL segments Redirect with UrlTree When a guard must redirect, return a UrlTree instead of returning false and calling navigate() . export const authGuard: CanActivateFn = ( route, state ) => { const authService = inject(AuthService); const router = inject(Router); return authService.isLoggedIn() ? true : router.createUrlTree( ['/login'], { queryParams: { returnUrl: state.url } } ); }; Asynchronous Guard A guard can return an Observable when authentication or permission state is asynchronous. import { inject } from '@angular/core'; import { CanActivateFn, Router } from '@angular/router'; import { map, take } from 'rxjs'; export const sessionGuard: CanActivateFn = () => { const authService = inject(AuthService); const router = inject(Router); return authService.session$.pipe( take(1), map(session => session ? true : router.createUrlTree( ['/login'] ) ) ); }; Functional Guards Modern Angular applications commonly use functional guards. export const authGuard: CanActivateFn = () => { const authService = inject(AuthService); return authService.isLoggedIn(); }; Functional guards are: Small and focused Easy to compose Easy to test Compatible with inject() Deprecated canLoad Do not use canLoad in new Angular applications. Use canMatch instead. Recommended: canMatch: [authGuard] Avoid for new code: canLoad: [authGuard] Testing a Functional Guard import { TestBed } from '@angular/core/testing'; import { Router } from '@angular/router'; describe('authGuard', () => { it('allows authenticated users', () => { TestBed.configureTestingModule({ providers: [ { provide: AuthService, useValue: { isLoggedIn: () => true } } ] }); const result = TestBed.runInInjectionContext( () => authGuard( {} as never, { url: '/dashboard' } as never ) ); expect(result).toBe(true); }); }); Security Warning Route guards run in the browser and must not be treated as complete security. A user can modify browser-side JavaScript or directly call backend APIs. Frontend Guard = Navigation Protection Backend Authorization = Actual Security Always validate authentication, roles, ownership, and permissions on the backend. Best Practices Use functional guards for new Angular applications Keep guard logic small and focused Move authentication logic into an authentication service Return a UrlTree or RedirectCommand for redirects Use canMatch for feature flags and conditional route selection Use canDeactivate to protect unsaved forms Avoid long-running API requests inside guards Do not use route guards as the only authorization layer Always enforce permissions on the backend Interview Questions What is a route guard? A route guard is a function that controls whether Angular Router should allow, block, redirect, or skip navigation. What is the difference between canActivate and canMatch? canActivate controls whether an already matched route can activate. canMatch controls whether Angular should use a route configuration during URL matching. What happens when canMatch returns false? Angular skips that route configuration and tries another matching route. How should a guard redirect a user? It should return a UrlTree or RedirectCommand rather than returning false and manually calling navigate() . Can route guards secure backend APIs? No. Route guards only control frontend navigation. Backend APIs must independently verify authentication and authorization. What replaced canLoad? Use canMatch in new Angular applications. Key Point Route Guard = A decision made before matching, entering, accessing children, or leaving an Angular route. Easy Formula Navigation Request ↓ Route Matching ↓ canMatch ↓ canActivate ↓ canActivateChild ↓ Component Opens ↓ canDeactivate ↓ Component Closes