Tutorials Logic, IN +91 8092939553 info@tutorialslogic.com
FAQs Support
Navigation
Home About Us Contact Us Blogs FAQs
Tutorials
All Tutorials
Services
Academic Projects Resume Writing Interview Questions Website Development
Compiler Tutorials

Angular Route Guards

What are Route Guards?

Route Guards are interfaces that tell the Angular Router whether to allow or deny navigation to a route. They are used to protect routes from unauthorized access, prevent users from leaving unsaved forms, or pre-fetch data before a route activates. In Angular 21, guards are implemented as simple functions (functional guards) rather than classes.

GuardPurpose
canActivateDecides if a route can be activated (navigated to)
canActivateChildDecides if child routes can be activated
canDeactivateDecides if a route can be left (e.g. unsaved changes)
canMatchDecides if a route definition should be matched at all
resolvePre-fetches data before the route activates

canActivate — Protecting Routes

The most common guard. Use it to check if a user is authenticated before allowing access to a protected route. In Angular 21, guards are plain functions that return boolean, UrlTree, or a Promise/Observable of either.

canActivate — Auth Guard
import { inject } from '@angular/core';
import { CanActivateFn, Router } from '@angular/router';
import { AuthService } from './auth.service';

// Functional guard — the modern Angular 21 approach
export const authGuard: CanActivateFn = (route, state) => {
    const auth   = inject(AuthService);
    const router = inject(Router);

    if (auth.isLoggedIn()) {
        return true;
    }

    // Redirect to login, preserving the intended URL
    return router.createUrlTree(['/login'], {
        queryParams: { returnUrl: state.url }
    });
};

// auth.service.ts (simplified)
// @Injectable({ providedIn: 'root' })
// export class AuthService {
//     private loggedIn = signal(false);
//     isLoggedIn() { return this.loggedIn(); }
//     login()  { this.loggedIn.set(true); }
//     logout() { this.loggedIn.set(false); }
// }
import { Routes } from '@angular/router';
import { authGuard } from './auth.guard';
import { DashboardComponent } from './dashboard/dashboard.component';
import { LoginComponent } from './login/login.component';

export const routes: Routes = [
    { path: 'login', component: LoginComponent },
    {
        path: 'dashboard',
        component: DashboardComponent,
        canActivate: [authGuard]   // protect this route
    },
    {
        path: 'admin',
        loadChildren: () => import('./admin/admin.routes').then(m => m.adminRoutes),
        canActivate: [authGuard]   // protect entire lazy-loaded section
    }
];

canDeactivate — Preventing Unsaved Changes

Use canDeactivate to warn users before they navigate away from a form with unsaved changes. The guard can check a component property or call a method on the component.

canDeactivate — Unsaved Changes Guard
import { CanDeactivateFn } from '@angular/router';

// Define an interface for components that can be guarded
export interface CanComponentDeactivate {
    canDeactivate: () => boolean;
}

export const unsavedChangesGuard: CanDeactivateFn<CanComponentDeactivate> = (component) => {
    if (component.canDeactivate()) {
        return true;
    }
    return confirm('You have unsaved changes. Leave anyway?');
};
import { Component, signal } from '@angular/core';
import { CanComponentDeactivate } from './unsaved-changes.guard';

@Component({
    selector: 'app-edit-form',
    standalone: true,
    template: `
        <input [(ngModel)]="name" (ngModelChange)="isDirty.set(true)" />
        <button (click)="save()">Save</button>
        <p *ngIf="isDirty()">You have unsaved changes</p>
    `
})
export class EditFormComponent implements CanComponentDeactivate {
    name   = '';
    isDirty = signal(false);

    save() {
        // ... save logic
        this.isDirty.set(false);
    }

    canDeactivate(): boolean {
        return !this.isDirty();
    }
}

resolve — Pre-fetching Data

A resolve guard pre-fetches data before the route activates. The component receives the resolved data via ActivatedRoute. This prevents the component from rendering with empty data.

resolve — Data Pre-fetching
import { inject } from '@angular/core';
import { ResolveFn } from '@angular/router';
import { HttpClient } from '@angular/common/http';

export interface User { id: number; name: string; email: string; }

export const userResolver: ResolveFn<User> = (route) => {
    const http = inject(HttpClient);
    const id   = route.paramMap.get('id');
    return http.get<User>(`https://api.example.com/users/${id}`);
};
import { Routes } from '@angular/router';
import { userResolver } from './user.resolver';
import { UserDetailComponent } from './user-detail.component';

export const routes: Routes = [
    {
        path: 'users/:id',
        component: UserDetailComponent,
        resolve: { user: userResolver }
    }
];

// In UserDetailComponent:
// constructor(private route: ActivatedRoute) {}
// ngOnInit() {
//     const user = this.route.snapshot.data['user'];
// }

Ready to Level Up Your Skills?

Explore 500+ free tutorials across 20+ languages and frameworks.