Angular Guards
Angular Guards are services that control whether the router can navigate to or away from a route. They act as gatekeepers — checking conditions before allowing or blocking navigation. Guards are the standard way to protect routes that require authentication, specific permissions, or certain conditions to be met before access is granted.
A practical analogy: a security guard at a building entrance checks credentials before allowing entry. Angular Guards work the same way — they check conditions before allowing the router to proceed.
Types of Guards
Angular provides several types of guards, each serving a specific purpose in the navigation lifecycle:
CanActivate
Runs before a route is activated. Returns true to allow navigation or false (or a redirect URL) to block it. This is the most commonly used guard — typically used to protect pages that require the user to be logged in.
CanActivateChild
Works the same as CanActivate but applies to all child routes of a route. Useful when an entire section (like an admin panel) requires protection with a single guard.
CanDeactivate
Runs before leaving a route. Useful for warning users about unsaved changes before navigating away from a form page. Returns true to allow leaving or false to keep the user on the current page.
CanLoad
Runs before a lazy-loaded module is downloaded. If the guard returns false, the module's code is not even downloaded — this provides a security benefit by not exposing module code to unauthorized users.
Resolve
Runs before a route is activated and fetches data that the component needs. The component does not load until the data is ready, preventing the component from rendering with empty data.
Creating a CanActivate Guard
Generate a guard with the Angular CLI:
ng generate guard guards/auth
Select CanActivate when prompted.
// guards/auth.guard.ts
import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from '@angular/router';
import { AuthService } from '../services/auth.service';
@Injectable({ providedIn: 'root' })
export class AuthGuard implements CanActivate {
constructor(
private authService: AuthService,
private router: Router
) {}
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
if (this.authService.isLoggedIn()) {
return true; // Allow navigation
} else {
// Save the attempted URL for redirecting after login
this.router.navigate(['/login'], { queryParams: { returnUrl: state.url } });
return false; // Block navigation
}
}
}
The AuthService
// services/auth.service.ts
import { Injectable } from '@angular/core';
import { Router } from '@angular/router';
@Injectable({ providedIn: 'root' })
export class AuthService {
private loggedIn = false;
private currentUser: any = null;
constructor(private router: Router) {
// Check localStorage for existing session on app load
const userData = localStorage.getItem('user');
if (userData) {
this.loggedIn = true;
this.currentUser = JSON.parse(userData);
}
}
login(username: string, password: string): boolean {
// In a real app, this would call an API
if (username === 'admin' && password === 'password123') {
this.loggedIn = true;
this.currentUser = { username, role: 'admin' };
localStorage.setItem('user', JSON.stringify(this.currentUser));
return true;
}
return false;
}
logout() {
this.loggedIn = false;
this.currentUser = null;
localStorage.removeItem('user');
this.router.navigate(['/login']);
}
isLoggedIn(): boolean {
return this.loggedIn;
}
getCurrentUser(): any {
return this.currentUser;
}
hasRole(role: string): boolean {
return this.currentUser?.role === role;
}
}
Applying the Guard to Routes
// app-routing.module.ts
import { AuthGuard } from './guards/auth.guard';
const routes: Routes = [
{ path: 'login', component: LoginComponent },
{ path: 'home', component: HomeComponent },
// Protected routes — require authentication
{
path: 'dashboard',
component: DashboardComponent,
canActivate: [AuthGuard]
},
{
path: 'profile',
component: ProfileComponent,
canActivate: [AuthGuard]
},
{
path: 'settings',
component: SettingsComponent,
canActivate: [AuthGuard],
children: [
{ path: 'general', component: GeneralSettingsComponent },
{ path: 'security', component: SecuritySettingsComponent }
]
}
];
Role-Based Guard with CanActivate
Guards can use route data to check user roles, allowing fine-grained access control:
// guards/role.guard.ts
import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, Router } from '@angular/router';
import { AuthService } from '../services/auth.service';
@Injectable({ providedIn: 'root' })
export class RoleGuard implements CanActivate {
constructor(
private authService: AuthService,
private router: Router
) {}
canActivate(route: ActivatedRouteSnapshot): boolean {
const requiredRole = route.data['role']; // Read expected role from route data
const userHasRole = this.authService.hasRole(requiredRole);
if (!this.authService.isLoggedIn()) {
this.router.navigate(['/login']);
return false;
}
if (!userHasRole) {
this.router.navigate(['/unauthorized']);
return false;
}
return true;
}
}
// Routes with role requirements defined as data
const routes: Routes = [
{
path: 'admin',
component: AdminComponent,
canActivate: [AuthGuard, RoleGuard],
data: { role: 'admin' } // ← Passed to the guard via route.data
},
{
path: 'reports',
component: ReportsComponent,
canActivate: [AuthGuard, RoleGuard],
data: { role: 'manager' }
}
];
CanActivateChild — Protecting All Child Routes
// app-routing.module.ts
{
path: 'admin',
component: AdminLayoutComponent,
canActivateChild: [AuthGuard], // Protects ALL child routes
children: [
{ path: 'users', component: UserManagementComponent },
{ path: 'reports', component: ReportsComponent },
{ path: 'settings', component: AdminSettingsComponent }
]
}
CanDeactivate — Warning About Unsaved Changes
This guard is particularly useful for form pages where the user might accidentally navigate away and lose their input:
// guards/unsaved-changes.guard.ts
import { Injectable } from '@angular/core';
import { CanDeactivate } from '@angular/router';
export interface CanComponentDeactivate {
hasUnsavedChanges(): boolean;
}
@Injectable({ providedIn: 'root' })
export class UnsavedChangesGuard implements CanDeactivate<CanComponentDeactivate> {
canDeactivate(component: CanComponentDeactivate): boolean {
if (component.hasUnsavedChanges()) {
return confirm('You have unsaved changes. Are you sure you want to leave?');
}
return true;
}
}
// edit-profile.component.ts — implements the interface
export class EditProfileComponent implements CanComponentDeactivate {
formIsDirty = false;
hasUnsavedChanges(): boolean {
return this.formIsDirty;
}
}
// Route configuration
{ path: 'edit-profile', component: EditProfileComponent, canDeactivate: [UnsavedChangesGuard] }
Resolve — Pre-fetching Data Before Navigation
A Resolve guard fetches data before the component activates, ensuring the component always has the data it needs from the very first render:
// resolvers/product.resolver.ts
import { Injectable } from '@angular/core';
import { Resolve, ActivatedRouteSnapshot } from '@angular/router';
import { Observable } from 'rxjs';
import { ProductService } from '../services/product.service';
@Injectable({ providedIn: 'root' })
export class ProductResolver implements Resolve<any> {
constructor(private productService: ProductService) {}
resolve(route: ActivatedRouteSnapshot): Observable<any> {
const id = route.paramMap.get('id');
return this.productService.getProductById(Number(id));
// Navigation waits until this Observable completes
}
}
// Route with resolver
{ path: 'products/:id', component: ProductDetailComponent, resolve: { product: ProductResolver } }
// Accessing the resolved data in the component
export class ProductDetailComponent implements OnInit {
product: any;
constructor(private route: ActivatedRoute) {}
ngOnInit() {
this.product = this.route.snapshot.data['product'];
// Data is available immediately — no loading state needed
}
}
Summary
Angular Guards protect routes and control navigation flow. CanActivate blocks access to routes based on conditions like authentication status. CanActivateChild applies the same protection to all child routes with a single guard. CanDeactivate prevents users from accidentally leaving pages with unsaved changes. CanLoad prevents lazy-loaded modules from downloading if the user is unauthorized. Resolve pre-fetches data before a component loads, ensuring the view renders with complete data from the start. Guards are added to route definitions using the canActivate, canActivateChild, canDeactivate, and resolve properties. Route data passed via the data property enables dynamic, role-based guard logic.
