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:
- CanActivate 🔒: Controls if a route can be activated
- CanDeactivate 🚪: Controls if user can leave a route
- CanLoad 📦: Controls if a module can be loaded
- Resolve 📊: Pre-loads data before route activation
- 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
- 🎯 Keep Guards Simple: One responsibility per guard
- 📝 Use Type Safety: Leverage TypeScript interfaces
- 🛡️ Test Your Guards: Write unit tests for guard logic
- 🎨 Clear Naming:
AuthGuard
,AdminGuard
,UnsavedChangesGuard
- ✨ Handle Edge Cases: Network errors, expired tokens, etc.
- 🔄 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:
- 💻 Practice with the exercises above
- 🏗️ Build a small app with multiple guard types
- 📚 Move on to our next tutorial: Angular HTTP Interceptors
- 🌟 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! 🎉🛡️✨