🛡️ Route Guards: The Gatekeepers of Your Angular App
The Story of the Castle Gates
Imagine you live in a magical castle with many rooms. 🏰
Some rooms are open to everyone—like the main hall. But other rooms? They’re special! The treasure room needs a key. The library needs permission from the librarian. And before you leave the art room, someone checks if you saved your painting!
Route Guards in Angular work exactly like castle gatekeepers. They stand at each door (route) and decide:
- ✅ “Yes, you may enter!”
- ❌ “Sorry, you can’t go in.”
- ⏳ “Wait, let me fetch something for you first.”
Let’s meet all five gatekeepers!
🎯 Route Guards Overview
What Are Route Guards?
Route Guards are special helpers that Angular calls before a user enters (or leaves) a page.
Think of it like this:
Before you walk into a new room, the guard checks your ticket. 🎫
Why Do We Need Them?
| Problem | Guard Solution |
|---|---|
| Stop strangers from entering secret pages | CanActivate |
| Warn users before they lose unsaved work | CanDeactivate |
| Block entire feature areas for certain users | CanMatch |
| Load data BEFORE the page shows | Resolve |
| Write guards as simple functions | Functional Guards |
The Guard Family
graph TD A["User Clicks Link"] --> B{Route Guards Check} B -->|CanMatch| C["Can this route exist?"] B -->|CanActivate| D["Can user enter?"] B -->|Resolve| E["Fetch data first"] B -->|CanDeactivate| F["Can user leave?"] C --> G["Route Loads or Blocks"] D --> G E --> G F --> G
🔐 CanActivate Guard
The “Can You Enter?” Guard
CanActivate is like a bouncer at a club. 🚪
“Show me your membership card before you come in!”
When to Use It
- Protect pages that need login
- Block users without the right role
- Redirect visitors to a login page
Simple Example
// auth.guard.ts
import { inject } from '@angular/core';
import { CanActivateFn, Router } from '@angular/router';
import { AuthService } from './auth.service';
export const authGuard: CanActivateFn = () => {
const auth = inject(AuthService);
const router = inject(Router);
if (auth.isLoggedIn()) {
return true; // ✅ Enter!
}
return router.parseUrl('/login'); // ❌ Go to login
};
Using It in Routes
// app.routes.ts
export const routes: Routes = [
{
path: 'dashboard',
component: DashboardComponent,
canActivate: [authGuard] // 🛡️ Guard here!
}
];
Real Life Analogy
Think of your favorite app:
- 📱 Instagram: Can’t see DMs until you log in
- 🎮 Games: Can’t access VIP levels without subscription
That’s CanActivate doing its job!
🚪 CanDeactivate Guard
The “Are You Sure You Want to Leave?” Guard
CanDeactivate is like a caring friend. 🤗
“Wait! You haven’t saved your work. Are you sure you want to go?”
When to Use It
- Forms with unsaved changes
- Documents being edited
- Shopping carts being abandoned
Simple Example
// unsaved-changes.guard.ts
import { CanDeactivateFn } from '@angular/router';
export interface CanLeave {
canLeave(): boolean;
}
export const unsavedGuard: CanDeactivateFn<CanLeave> =
(component) => {
if (component.canLeave()) {
return true; // ✅ Go ahead!
}
// ⚠️ Ask user
return confirm('Leave without saving?');
};
Using It with a Component
// edit.component.ts
@Component({...})
export class EditComponent implements CanLeave {
hasUnsavedChanges = false;
canLeave(): boolean {
return !this.hasUnsavedChanges;
}
}
Route Setup
{
path: 'edit/:id',
component: EditComponent,
canDeactivate: [unsavedGuard] // 🛡️ Check before leaving
}
Real Life Analogy
It’s like:
- 📝 Google Docs asking “Discard changes?”
- 🛒 Amazon warning “Items in cart will be lost!”
🎯 CanMatch Guard
The “Should This Route Even Exist?” Guard
CanMatch is the smartest guard. 🧠
“Different users see different routes!”
What Makes It Special?
| Guard | Question |
|---|---|
| CanActivate | Can user access this route? |
| CanMatch | Should this route even load? |
When to Use It
- Different layouts for different roles
- A/B testing different features
- Feature flags
Simple Example
// admin-match.guard.ts
import { inject } from '@angular/core';
import { CanMatchFn } from '@angular/router';
import { AuthService } from './auth.service';
export const adminMatch: CanMatchFn = () => {
const auth = inject(AuthService);
return auth.isAdmin(); // true/false
};
Using It for Role-Based Routes
// app.routes.ts
export const routes: Routes = [
{
path: 'dashboard',
loadComponent: () => import('./admin-dash'),
canMatch: [adminMatch] // 👑 Admins only
},
{
path: 'dashboard',
loadComponent: () => import('./user-dash')
// 👤 Everyone else gets this
}
];
How It Works
graph TD A["User visits /dashboard"] --> B{canMatch: adminMatch} B -->|Admin? Yes| C["Load AdminDashboard"] B -->|Admin? No| D["Try next route"] D --> E["Load UserDashboard"]
Real Life Analogy
Like a hotel:
- 🎩 VIP guests see the fancy menu
- 👤 Regular guests see the standard menu
- Same door, different experience!
📦 Resolve Guard
The “Let Me Get That For You” Guard
Resolve is like a helpful waiter. 🍽️
“Before you sit down, let me bring your food to the table!”
The Problem It Solves
Without Resolve:
- Page loads empty
- User sees loading spinner
- Data finally appears
With Resolve:
- Data loads first
- Page appears complete! ✨
Simple Example
// user.resolver.ts
import { inject } from '@angular/core';
import { ResolveFn } from '@angular/router';
import { UserService } from './user.service';
import { User } from './user.model';
export const userResolver: ResolveFn<User> = (route) => {
const userService = inject(UserService);
const userId = route.paramMap.get('id')!;
return userService.getUser(userId);
};
Using It in Routes
{
path: 'profile/:id',
component: ProfileComponent,
resolve: {
userData: userResolver // 📦 Data ready!
}
}
Accessing Resolved Data
// profile.component.ts
@Component({...})
export class ProfileComponent {
private route = inject(ActivatedRoute);
user = this.route.snapshot.data['userData'];
// or
user$ = this.route.data.pipe(
map(data => data['userData'])
);
}
Real Life Analogy
Like ordering food:
- 🍔 Fast food: You wait, then food comes (no resolve)
- 🍽️ Fine dining: Food arrives when you sit (resolve!)
⚡ Functional Guards
The New, Simpler Way
Angular 15+ introduced Functional Guards. They’re just functions!
No classes. No implements. Just simple functions.
Old Way (Class-Based) vs New Way (Functional)
Old Way:
@Injectable({providedIn: 'root'})
export class AuthGuard implements CanActivate {
constructor(private auth: AuthService) {}
canActivate(): boolean {
return this.auth.isLoggedIn();
}
}
New Way (Functional):
export const authGuard: CanActivateFn = () => {
return inject(AuthService).isLoggedIn();
};
Why Functional Guards Are Better
| Feature | Class Guards | Functional Guards |
|---|---|---|
| Code size | More lines | Fewer lines |
| Testability | Good | Better |
| Flexibility | Fixed | Composable |
| Learning curve | Steeper | Easier |
All Guard Types as Functions
// CanActivate
export const authGuard: CanActivateFn = () => {
return inject(AuthService).isLoggedIn();
};
// CanDeactivate
export const saveGuard: CanDeactivateFn<EditComponent> =
(component) => component.canLeave();
// CanMatch
export const adminMatch: CanMatchFn = () => {
return inject(AuthService).isAdmin();
};
// Resolve
export const dataResolver: ResolveFn<Data> = () => {
return inject(DataService).getData();
};
Composing Guards
The best part? Combine them easily!
// Combine multiple checks
export const superGuard: CanActivateFn = () => {
const auth = inject(AuthService);
const feature = inject(FeatureService);
return auth.isLoggedIn() && feature.isEnabled('beta');
};
🗺️ Putting It All Together
Here’s a complete route setup using all guards:
// app.routes.ts
export const routes: Routes = [
{
path: 'admin',
canMatch: [adminMatch], // 1️⃣ Route exists?
canActivate: [authGuard], // 2️⃣ Can enter?
resolve: {
settings: settingsResolver // 3️⃣ Load data
},
children: [
{
path: 'edit',
component: EditComponent,
canDeactivate: [unsavedGuard] // 4️⃣ Can leave?
}
]
}
];
Guard Execution Order
graph TD A["Navigate to Route"] --> B["CanMatch"] B -->|Pass| C["CanActivate"] B -->|Fail| Z["Try Next Route"] C -->|Pass| D["Resolve"] C -->|Fail| Y["Block/Redirect"] D --> E["Load Component"] E --> F["User Wants to Leave"] F --> G["CanDeactivate"] G -->|Pass| H["Navigate Away"] G -->|Fail| I["Stay on Page"]
🎉 You Did It!
You now know all five Angular Route Guards:
| Guard | Purpose | Analogy |
|---|---|---|
| CanActivate | Can user enter? | Bouncer at door |
| CanDeactivate | Can user leave? | Caring friend |
| CanMatch | Should route exist? | VIP menu selector |
| Resolve | Load data first | Helpful waiter |
| Functional | Simple function style | Modern & clean |
Remember This
Guards are your castle’s gatekeepers. They protect, prepare, and guide users through your app safely!
🏰 Now go build amazing, protected routes! 🚀
