Route Guards

Back

Loading concept...

🛡️ 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:

  1. Page loads empty
  2. User sees loading spinner
  3. Data finally appears

With Resolve:

  1. Data loads first
  2. 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! 🚀

Loading story...

Story - Premium Content

Please sign in to view this story and start learning.

Upgrade to Premium to unlock full access to all stories.

Stay Tuned!

Story is coming soon.

Story Preview

Story - Premium Content

Please sign in to view this concept and start learning.

Upgrade to Premium to unlock full access to all content.