+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Part 175 of 355

🛡 ️ Angular Routing: Navigation Guards

Master angular routing: navigation guards in TypeScript with practical examples, best practices, and real-world applications 🚀

🚀Intermediate
25 min read

Prerequisites

  • Basic understanding of JavaScript 📝
  • TypeScript installation ⚡
  • VS Code or preferred IDE 💻
  • Angular fundamentals 🅰️
  • Basic routing concepts 🛤️

What you'll learn

  • Understand navigation guard fundamentals 🎯
  • Apply guards in real Angular projects 🏗️
  • Debug common guard issues 🐛
  • Write type-safe guard code ✨

🎯 Introduction

Welcome to the exciting world of Angular Navigation Guards! 🎉 Think of guards as the bouncers of your Angular application - they decide who gets in and who gets stopped at the door! 🚪

Navigation guards are powerful TypeScript tools that let you control access to your routes, protect sensitive pages, and create smooth user experiences. Whether you’re building an e-commerce site 🛒, a banking app 🏦, or a social platform 📱, understanding guards is essential for creating secure, user-friendly applications.

By the end of this tutorial, you’ll be confidently implementing guards to protect your routes and control navigation flow! Let’s dive in! 🏊‍♂️

📚 Understanding Navigation Guards

🤔 What are Navigation Guards?

Navigation guards are like security checkpoints at an airport ✈️. Just as security checks passengers before they board, guards check conditions before allowing navigation to routes!

In Angular terms, guards are TypeScript classes or functions that implement specific interfaces to control route access. This means you can:

  • ✨ Protect authenticated routes
  • 🚀 Prevent navigation away from unsaved forms
  • 🛡️ Check user permissions
  • 🔄 Redirect users to appropriate pages

💡 Types of Guards

Angular provides several types of guards for different scenarios:

  1. CanActivate 🔒: Controls if a route can be activated
  2. CanDeactivate 🚪: Controls if user can leave a route
  3. CanLoad 📦: Controls if a module can be loaded
  4. Resolve 📊: Pre-loads data before route activation
  5. CanActivateChild 👶: Controls access to child routes

Real-world example: Imagine an online banking app 🏦. You’d use CanActivate to ensure only logged-in users can access account pages, and CanDeactivate to warn users before leaving a money transfer form!

🔧 Basic Syntax and Usage

📝 Simple CanActivate Guard

Let’s start with a friendly authentication guard:

// 🛡️ auth.guard.ts - Protecting our routes!
import { Injectable } from '@angular/core';
import { CanActivate, Router } from '@angular/router';

@Injectable({
  providedIn: 'root'
})
export class AuthGuard implements CanActivate {
  
  constructor(private router: Router) {}
  
  // 🔍 Check if user can access the route
  canActivate(): boolean {
    const isLoggedIn = this.checkAuthentication();
    
    if (!isLoggedIn) {
      console.log("🚫 Access denied! Please log in.");
      this.router.navigate(['/login']); // 🔄 Redirect to login
      return false;
    }
    
    console.log("✅ Welcome! Access granted.");
    return true;
  }
  
  // 🔐 Mock authentication check
  private checkAuthentication(): boolean {
    // In real app, check JWT token, session, etc.
    return localStorage.getItem('userToken') !== null;
  }
}

💡 Explanation: This guard checks if a user is authenticated before allowing route access. If not logged in, it redirects to the login page!

🎯 Applying Guards to Routes

Here’s how to use guards in your routing configuration:

// 🛤️ app-routing.module.ts - Setting up protected routes
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { AuthGuard } from './guards/auth.guard';

const routes: Routes = [
  {
    path: 'home',
    component: HomeComponent
  },
  {
    path: 'profile',
    component: ProfileComponent,
    canActivate: [AuthGuard] // 🛡️ Protected route!
  },
  {
    path: 'dashboard',
    component: DashboardComponent,
    canActivate: [AuthGuard] // 🔒 Another protected route
  },
  {
    path: 'login',
    component: LoginComponent
  },
  {
    path: '',
    redirectTo: '/home',
    pathMatch: 'full'
  }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

💡 Practical Examples

🛒 Example 1: E-commerce Cart Guard

Let’s build a realistic shopping cart protection system:

// 🛍️ cart.guard.ts - Protecting shopping cart access
import { Injectable } from '@angular/core';
import { CanActivate, Router, ActivatedRouteSnapshot } from '@angular/router';

interface CartItem {
  id: string;
  name: string;
  price: number;
  emoji: string;
}

@Injectable({
  providedIn: 'root'
})
export class CartGuard implements CanActivate {
  
  constructor(private router: Router) {}
  
  canActivate(route: ActivatedRouteSnapshot): boolean {
    const cartItems = this.getCartItems();
    
    // 🛒 Check if cart has items
    if (cartItems.length === 0) {
      console.log("🛒 Your cart is empty! Let's go shopping! 🛍️");
      this.router.navigate(['/products']);
      return false;
    }
    
    // 💰 Check minimum order value
    const total = this.calculateTotal(cartItems);
    const minimumOrder = 25;
    
    if (total < minimumOrder) {
      console.log(`💸 Minimum order is $${minimumOrder}. Current: $${total}`);
      this.router.navigate(['/products'], {
        queryParams: { message: 'minimum-not-met' }
      });
      return false;
    }
    
    console.log("✅ Cart looks good! Proceeding to checkout 🎉");
    return true;
  }
  
  // 🛒 Get cart items from service/storage
  private getCartItems(): CartItem[] {
    const cartData = localStorage.getItem('cart');
    return cartData ? JSON.parse(cartData) : [];
  }
  
  // 💰 Calculate total price
  private calculateTotal(items: CartItem[]): number {
    return items.reduce((sum, item) => sum + item.price, 0);
  }
}

// 🎯 Usage in routing
const routes: Routes = [
  {
    path: 'checkout',
    component: CheckoutComponent,
    canActivate: [CartGuard] // 🛡️ Protect checkout process
  }
];

🎯 Try it yourself: Add a guard that checks for a valid shipping address before checkout!

🎮 Example 2: Game Progress Guard

Let’s create a fun gaming application guard:

// 🎮 game-progress.guard.ts - Level progression protection
import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, Router } from '@angular/router';

interface PlayerProgress {
  level: number;
  experience: number;
  achievements: string[];
  unlockedLevels: number[];
}

@Injectable({
  providedIn: 'root'
})
export class GameProgressGuard implements CanActivate {
  
  constructor(private router: Router) {}
  
  canActivate(route: ActivatedRouteSnapshot): boolean {
    const levelId = Number(route.params['level']);
    const playerProgress = this.getPlayerProgress();
    
    // 🎯 Check if player has unlocked this level
    if (!this.isLevelUnlocked(levelId, playerProgress)) {
      console.log(`🔒 Level ${levelId} is locked! Complete previous levels first.`);
      this.showLevelLockedMessage(levelId, playerProgress);
      this.router.navigate(['/game/levels']);
      return false;
    }
    
    // 🏆 Check if player meets level requirements
    if (!this.meetsLevelRequirements(levelId, playerProgress)) {
      console.log(`⚡ You need more experience for level ${levelId}!`);
      this.router.navigate(['/game/training']);
      return false;
    }
    
    console.log(`🎮 Welcome to level ${levelId}! Good luck! 🍀`);
    return true;
  }
  
  // 🔓 Check if level is unlocked
  private isLevelUnlocked(levelId: number, progress: PlayerProgress): boolean {
    return progress.unlockedLevels.includes(levelId);
  }
  
  // ⚡ Check experience requirements
  private meetsLevelRequirements(levelId: number, progress: PlayerProgress): boolean {
    const requiredExp = levelId * 100; // Each level needs 100 more exp
    return progress.experience >= requiredExp;
  }
  
  // 👤 Get player progress
  private getPlayerProgress(): PlayerProgress {
    const defaultProgress: PlayerProgress = {
      level: 1,
      experience: 0,
      achievements: ["🌟 First Steps"],
      unlockedLevels: [1]
    };
    
    const saved = localStorage.getItem('gameProgress');
    return saved ? JSON.parse(saved) : defaultProgress;
  }
  
  // 🔒 Show level locked message
  private showLevelLockedMessage(levelId: number, progress: PlayerProgress): void {
    const nextUnlock = Math.max(...progress.unlockedLevels) + 1;
    console.log(`💡 Complete level ${nextUnlock - 1} to unlock level ${levelId}!`);
  }
}

🚀 Advanced Concepts

🧙‍♂️ CanDeactivate Guard: Preventing Data Loss

When you’re ready to level up, try this advanced pattern for protecting unsaved changes:

// 💾 unsaved-changes.guard.ts - Protecting user data!
import { Injectable } from '@angular/core';
import { CanDeactivate } from '@angular/router';
import { Observable } from 'rxjs';

// 📝 Interface for components with unsaved changes
export interface CanComponentDeactivate {
  canDeactivate(): Observable<boolean> | Promise<boolean> | boolean;
  hasUnsavedChanges(): boolean;
}

@Injectable({
  providedIn: 'root'
})
export class UnsavedChangesGuard implements CanDeactivate<CanComponentDeactivate> {
  
  canDeactivate(component: CanComponentDeactivate): boolean | Observable<boolean> {
    // 📊 Check if component has unsaved changes
    if (component.hasUnsavedChanges()) {
      // 🚨 Show confirmation dialog
      const message = "🚨 You have unsaved changes! Are you sure you want to leave?";
      return confirm(message);
    }
    
    return true; // ✅ No changes, safe to leave
  }
}

// 📝 Example component implementing the interface
export class EditProfileComponent implements CanComponentDeactivate {
  private formChanged = false;
  
  // 🔄 Track form changes
  onFormChange(): void {
    this.formChanged = true;
  }
  
  // 💾 Save changes
  onSave(): void {
    // Save logic here...
    this.formChanged = false;
    console.log("✅ Profile saved successfully! 🎉");
  }
  
  // 🔍 Check for unsaved changes
  hasUnsavedChanges(): boolean {
    return this.formChanged;
  }
  
  // 🚪 Can we leave this component?
  canDeactivate(): boolean {
    return !this.hasUnsavedChanges();
  }
}

🏗️ Async Guards with Observables

For advanced scenarios with async operations:

// 🔄 async-permission.guard.ts - Async permission checking
import { Injectable } from '@angular/core';
import { CanActivate, Router } from '@angular/router';
import { Observable, of } from 'rxjs';
import { map, catchError } from 'rxjs/operators';

@Injectable({
  providedIn: 'root'
})
export class AsyncPermissionGuard implements CanActivate {
  
  constructor(private router: Router) {}
  
  canActivate(): Observable<boolean> {
    return this.checkUserPermissions().pipe(
      map((hasPermission: boolean) => {
        if (!hasPermission) {
          console.log("🚫 Insufficient permissions!");
          this.router.navigate(['/unauthorized']);
          return false;
        }
        return true;
      }),
      catchError(() => {
        console.log("❌ Error checking permissions!");
        this.router.navigate(['/error']);
        return of(false);
      })
    );
  }
  
  // 🔍 Simulate async permission check
  private checkUserPermissions(): Observable<boolean> {
    return new Observable<boolean>(observer => {
      // 🕒 Simulate API call delay
      setTimeout(() => {
        const userRole = localStorage.getItem('userRole');
        observer.next(userRole === 'admin' || userRole === 'moderator');
        observer.complete();
      }, 1000);
    });
  }
}

⚠️ Common Pitfalls and Solutions

😱 Pitfall 1: Forgetting to Return Boolean

// ❌ Wrong way - guard doesn't work!
export class BadGuard implements CanActivate {
  canActivate(): boolean {
    if (!this.isAuthenticated()) {
      this.router.navigate(['/login']);
      // 💥 Forgot to return false!
    }
    return true;
  }
}

// ✅ Correct way - always return explicitly!
export class GoodGuard implements CanActivate {
  canActivate(): boolean {
    if (!this.isAuthenticated()) {
      this.router.navigate(['/login']);
      return false; // ✅ Explicit return!
    }
    return true;
  }
}

🤯 Pitfall 2: Infinite Redirect Loops

// ❌ Dangerous - might create infinite loops!
export class RedirectGuard implements CanActivate {
  canActivate(): boolean {
    if (!this.isAuthenticated()) {
      this.router.navigate(['/profile']); // 💥 If /profile uses this guard!
      return false;
    }
    return true;
  }
}

// ✅ Safe - redirect to unguarded route!
export class SafeRedirectGuard implements CanActivate {
  canActivate(): boolean {
    if (!this.isAuthenticated()) {
      this.router.navigate(['/login']); // ✅ Login page has no guards
      return false;
    }
    return true;
  }
}

🛠️ Best Practices

  1. 🎯 Keep Guards Simple: One responsibility per guard
  2. 📝 Use Type Safety: Leverage TypeScript interfaces
  3. 🛡️ Test Your Guards: Write unit tests for guard logic
  4. 🎨 Clear Naming: AuthGuard, AdminGuard, UnsavedChangesGuard
  5. ✨ Handle Edge Cases: Network errors, expired tokens, etc.
  6. 🔄 Provide User Feedback: Show loading states and error messages

🧪 Hands-On Exercise

🎯 Challenge: Build a Multi-Level Security System

Create a comprehensive guard system for a company portal:

📋 Requirements:

  • ✅ Authentication guard for all protected routes
  • 🏷️ Role-based access (Admin, Manager, Employee)
  • 👤 Department-specific access control
  • 📅 Time-based access (business hours only)
  • 🎨 Prevent access during maintenance mode

🚀 Bonus Points:

  • Add async permission checking
  • Implement unsaved changes protection
  • Create a guard composition system
  • Add comprehensive error handling

💡 Solution

🔍 Click to see solution
// 🎯 Our comprehensive security system!

// 🔐 Base authentication guard
@Injectable({
  providedIn: 'root'
})
export class AuthGuard implements CanActivate {
  constructor(private router: Router, private authService: AuthService) {}
  
  canActivate(): boolean {
    if (!this.authService.isAuthenticated()) {
      this.router.navigate(['/login']);
      return false;
    }
    return true;
  }
}

// 👔 Role-based access guard
@Injectable({
  providedIn: 'root'
})
export class RoleGuard implements CanActivate {
  constructor(private router: Router, private authService: AuthService) {}
  
  canActivate(route: ActivatedRouteSnapshot): boolean {
    const requiredRoles = route.data['roles'] as string[];
    const userRole = this.authService.getUserRole();
    
    if (!requiredRoles.includes(userRole)) {
      console.log("🚫 Insufficient role permissions!");
      this.router.navigate(['/unauthorized']);
      return false;
    }
    
    return true;
  }
}

// 🏢 Department access guard
@Injectable({
  providedIn: 'root'
})
export class DepartmentGuard implements CanActivate {
  constructor(private router: Router, private authService: AuthService) {}
  
  canActivate(route: ActivatedRouteSnapshot): boolean {
    const requiredDepartment = route.data['department'] as string;
    const userDepartment = this.authService.getUserDepartment();
    
    if (userDepartment !== requiredDepartment) {
      console.log("🏢 Department access denied!");
      this.router.navigate(['/dashboard']);
      return false;
    }
    
    return true;
  }
}

// 🕒 Business hours guard
@Injectable({
  providedIn: 'root'
})
export class BusinessHoursGuard implements CanActivate {
  constructor(private router: Router) {}
  
  canActivate(): boolean {
    const now = new Date();
    const currentHour = now.getHours();
    const isWeekend = now.getDay() === 0 || now.getDay() === 6;
    
    // Business hours: 9 AM - 6 PM, Monday-Friday
    if (isWeekend || currentHour < 9 || currentHour >= 18) {
      console.log("🕒 Access only during business hours (9 AM - 6 PM, Mon-Fri)!");
      this.router.navigate(['/after-hours']);
      return false;
    }
    
    return true;
  }
}

// 🛠️ Maintenance mode guard
@Injectable({
  providedIn: 'root'
})
export class MaintenanceGuard implements CanActivate {
  constructor(private router: Router) {}
  
  canActivate(): boolean {
    const maintenanceMode = localStorage.getItem('maintenanceMode') === 'true';
    
    if (maintenanceMode) {
      console.log("🛠️ System under maintenance!");
      this.router.navigate(['/maintenance']);
      return false;
    }
    
    return true;
  }
}

// 🎯 Route configuration with multiple guards
const routes: Routes = [
  {
    path: 'admin',
    component: AdminComponent,
    canActivate: [
      AuthGuard,
      RoleGuard,
      BusinessHoursGuard,
      MaintenanceGuard
    ],
    data: { roles: ['admin'] }
  },
  {
    path: 'hr',
    component: HRComponent,
    canActivate: [AuthGuard, RoleGuard, DepartmentGuard],
    data: { 
      roles: ['admin', 'manager'], 
      department: 'HR' 
    }
  }
];

🎓 Key Takeaways

You’ve learned so much about Angular Navigation Guards! Here’s what you can now do:

  • Create various guard types with confidence 💪
  • Protect routes effectively using TypeScript 🛡️
  • Handle async operations in guards 🔄
  • Avoid common pitfalls that trip up developers 🐛
  • Build complex security systems for real applications! 🚀

Remember: Guards are your application’s security team - use them wisely to create secure, user-friendly experiences! 🤝

🤝 Next Steps

Congratulations! 🎉 You’ve mastered Angular Navigation Guards!

Here’s what to do next:

  1. 💻 Practice with the exercises above
  2. 🏗️ Build a small app with multiple guard types
  3. 📚 Move on to our next tutorial: Angular HTTP Interceptors
  4. 🌟 Share your guard implementations with the community!

Remember: Every Angular expert was once a beginner. Keep coding, keep learning, and most importantly, keep your routes secure! 🚀


Happy guarding! 🎉🛡️✨