← Back to Article List         
Angular Route Guards: Class-Based vs Functional Guards

Angular Route Guards: Class-Based vs Functional Guards

Published on 30 Sep 2026     8 min read Angular
Route Guards

Angular Route Guards: Class-Based vs Functional Guards

These three questions are closely related. The major change is:

Older Angular
     ↓
Class-based Guards
     ↓
implements CanActivate


Modern Angular
     ↓
Functional Guards
     ↓
CanActivateFn + inject()

1. How Were Class-Based Guards Implemented?

What is a Class-Based Guard?

A class-based guard is the traditional Angular approach where we create a class that implements a Router guard interface such as:

CanActivate
CanActivateChild
CanDeactivate
CanMatch

For authentication, the class commonly implemented:

CanActivate

and Angular's Dependency Injection provided services through the constructor.


Full Class-Based Authentication Guard Example

auth.service.ts

import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class AuthService {

  private loggedIn = false;

  login(): void {
    this.loggedIn = true;
  }

  logout(): void {
    this.loggedIn = false;
  }

  isLoggedIn(): boolean {
    return this.loggedIn;
  }
}

auth.guard.ts

import { Injectable } from '@angular/core';
import {
  ActivatedRouteSnapshot,
  CanActivate,
  Router,
  RouterStateSnapshot
} from '@angular/router';

import { AuthService } from './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;
    }

    this.router.navigate(['/login']);

    return false;
  }
}

app.routes.ts

import { Routes } from '@angular/router';

import { Dashboard } from './dashboard';
import { Login } from './login';

import { AuthGuard } from './auth.guard';

export const routes: Routes = [
  {
    path: 'login',
    component: Login
  },
  {
    path: 'dashboard',
    component: Dashboard,
    canActivate: [AuthGuard]
  }
];

How Class-Based Guard Works

The class:

export class AuthGuard implements CanActivate

implements:

CanActivate

Therefore, it must provide:

canActivate(...)

Angular executes this method before activating the route.

/dashboard
     ↓
AuthGuard
     ↓
canActivate()
     ↓
isLoggedIn()?
   /          \
 Yes           No
  ↓             ↓
true         /login
  ↓
Dashboard

Dependencies were normally constructor-injected:

constructor(
  private authService: AuthService,
  private router: Router
)

And because the guard was a DI-managed class, it commonly had:

@Injectable({
  providedIn: 'root'
})

2. What Are Functional Route Guards?

What is a Functional Guard?

A functional route guard is a guard implemented as a function instead of a class.

Modern Angular commonly uses functional guard types such as:

CanActivateFn
CanActivateChildFn
CanDeactivateFn
CanMatchFn

Instead of:

class AuthGuard implements CanActivate

we can write:

export const authGuard: CanActivateFn = () => {
  // guard logic
};

Full Functional Guard Example

auth.guard.ts

import { inject } from '@angular/core';
import {
  CanActivateFn,
  Router
} from '@angular/router';

import { AuthService } from './auth.service';

export const authGuard: CanActivateFn =
  (route, state) => {

    const authService = inject(AuthService);
    const router = inject(Router);

    if (authService.isLoggedIn()) {
      return true;
    }

    return router.createUrlTree(['/login']);
  };

Route configuration:

{
  path: 'dashboard',
  component: Dashboard,
  canActivate: [authGuard]
}

Notice the difference:

Older

canActivate: [AuthGuard]

AuthGuard is a class.

Modern

canActivate: [authGuard]

authGuard is a function.


How Does Dependency Injection Work?

In a class-based guard, dependencies were commonly injected through the constructor:

constructor(
  private authService: AuthService,
  private router: Router
)

In a functional guard, we commonly use:

inject()

For example:

const authService = inject(AuthService);
const router = inject(Router);

So:

Class Guard
    ↓
Constructor Injection


Functional Guard
    ↓
inject()

Class-Based vs Functional Guard

Class-Based Functional
Traditional approach Modern approach
Uses a class Uses a function
implements CanActivate CanActivateFn
Commonly uses constructor injection Uses inject()
Requires more boilerplate Less boilerplate
AuthGuard authGuard

Class

@Injectable({
  providedIn: 'root'
})
export class AuthGuard implements CanActivate {

  constructor(
    private authService: AuthService
  ) {
  }

  canActivate(): boolean {
    return this.authService.isLoggedIn();
  }
}

Functional

export const authGuard: CanActivateFn = () => {

  const authService = inject(AuthService);

  return authService.isLoggedIn();
};

The functional version is much smaller.


3. How Do You Create a Functional Authentication Guard?

Let's build a complete standalone Angular example.

The requirement is:

/dashboard

should only be accessible when the user is logged in.

Otherwise:

/dashboard
     ↓
Not authenticated
     ↓
/login

Full Program

auth.service.ts

import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class AuthService {

  private loggedIn = false;

  login(): void {
    this.loggedIn = true;
  }

  logout(): void {
    this.loggedIn = false;
  }

  isLoggedIn(): boolean {
    return this.loggedIn;
  }
}

auth.guard.ts

import { inject } from '@angular/core';
import {
  CanActivateFn,
  Router
} from '@angular/router';

import { AuthService } from './auth.service';

export const authGuard: CanActivateFn =
  (route, state) => {

    const authService = inject(AuthService);
    const router = inject(Router);

    if (authService.isLoggedIn()) {
      return true;
    }

    return router.createUrlTree(['/login']);
  };

login.ts

import { Component } from '@angular/core';
import { Router } from '@angular/router';

import { AuthService } from './auth.service';

@Component({
  selector: 'app-login',
  standalone: true,
  template: `
    <h2>Login</h2>

    <button (click)="login()">
      Login
    </button>
  `
})
export class Login {

  constructor(
    private authService: AuthService,
    private router: Router
  ) {
  }

  login(): void {

    this.authService.login();

    this.router.navigate(['/dashboard']);
  }
}

dashboard.ts

import { Component } from '@angular/core';

@Component({
  selector: 'app-dashboard',
  standalone: true,
  template: `
    <h2>Dashboard</h2>

    <p>
      Welcome! You are authenticated.
    </p>
  `
})
export class Dashboard {
}

app.routes.ts

import { Routes } from '@angular/router';

import { Login } from './login';
import { Dashboard } from './dashboard';

import { authGuard } from './auth.guard';

export const routes: Routes = [
  {
    path: 'login',
    component: Login
  },

  {
    path: 'dashboard',
    component: Dashboard,
    canActivate: [authGuard]
  },

  {
    path: '',
    redirectTo: 'login',
    pathMatch: 'full'
  }
];

app.ts

import { Component } from '@angular/core';
import {
  RouterLink,
  RouterOutlet
} from '@angular/router';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [
    RouterLink,
    RouterOutlet
  ],
  template: `
    <h1>Authentication Guard Demo</h1>

    <nav>
      <a routerLink="/login">Login</a>
      |
      <a routerLink="/dashboard">Dashboard</a>
    </nav>

    <hr>

    <router-outlet></router-outlet>
  `
})
export class App {
}

app.config.ts

import { ApplicationConfig } from '@angular/core';
import { provideRouter } from '@angular/router';

import { routes } from './app.routes';

export const appConfig: ApplicationConfig = {
  providers: [
    provideRouter(routes)
  ]
};

main.ts

import { bootstrapApplication }
  from '@angular/platform-browser';

import { App } from './app/app';
import { appConfig } from './app/app.config';

bootstrapApplication(App, appConfig)
  .catch(err => console.error(err));

How the Functional Authentication Guard Works

The protected route is:

{
  path: 'dashboard',
  component: Dashboard,
  canActivate: [authGuard]
}

When the user requests:

/dashboard

Angular executes:

authGuard

The guard gets the authentication service:

const authService = inject(AuthService);

and Router:

const router = inject(Router);

Then:

if (authService.isLoggedIn()) {
  return true;
}

If authenticated:

/dashboard
     ↓
authGuard
     ↓
isLoggedIn()
     ↓
true
     ↓
Dashboard

If not authenticated:

return router.createUrlTree(['/login']);

Flow:

/dashboard
     ↓
authGuard
     ↓
isLoggedIn()
     ↓
false
     ↓
/login

Why Return UrlTree Instead of navigate()?

You may see code like:

if (!authService.isLoggedIn()) {

  router.navigate(['/login']);

  return false;
}

It can work, but a cleaner guard pattern is to return the redirect:

return router.createUrlTree(['/login']);

The guard is then simply telling Angular Router:

Don't activate /dashboard
        ↓
Redirect to /login

rather than manually starting another navigation from inside the guard.


Functional Guard Types

Modern Angular provides functional equivalents for the major route guards:

Purpose Functional Type
Protect entering route CanActivateFn
Protect child routes CanActivateChildFn
Protect leaving route CanDeactivateFn<T>
Control route matching CanMatchFn

For example:

const guard: CanActivateFn
const guard: CanActivateChildFn
const guard: CanDeactivateFn<MyComponent>
const guard: CanMatchFn

Advantages of Functional Guards

  • Less boilerplate.
  • No guard class required.
  • No implements CanActivate.
  • No @Injectable() needed for the guard function itself.
  • Works naturally with inject().
  • Easy to read for simple authentication/authorization logic.
  • Fits modern standalone Angular patterns well.

Key Points

Older Angular

@Injectable({
  providedIn: 'root'
})
export class AuthGuard implements CanActivate {

  constructor(
    private authService: AuthService
  ) {
  }

  canActivate(): boolean {
    return this.authService.isLoggedIn();
  }
}

Modern Angular

export const authGuard: CanActivateFn = () => {

  const authService = inject(AuthService);

  return authService.isLoggedIn();
};

Remember:

OLD
 ↓
Class
 ↓
implements CanActivate
 ↓
Constructor DI


MODERN
 ↓
Function
 ↓
CanActivateFn
 ↓
inject()

Interview Questions and Answers

1. What is a class-based route guard?

A class-based route guard is the traditional Angular approach where a class implements a Router guard interface such as CanActivate.

export class AuthGuard implements CanActivate {
}

Dependencies are commonly provided using constructor injection.


2. What is a functional route guard?

A functional route guard implements route-guard logic using a function rather than a class.

For example:

export const authGuard: CanActivateFn = () => {
  return true;
};

3. How do you inject a service into a functional guard?

Use Angular's inject() function:

const authService = inject(AuthService);

4. How do you create a functional authentication guard?

export const authGuard: CanActivateFn = () => {

  const authService = inject(AuthService);
  const router = inject(Router);

  if (authService.isLoggedIn()) {
    return true;
  }

  return router.createUrlTree(['/login']);
};

Then configure it:

{
  path: 'dashboard',
  component: Dashboard,
  canActivate: [authGuard]
}

5. Class-based vs functional guards?

Class-based guards use classes and guard interfaces such as:

implements CanActivate

Functional guards use function types such as:

CanActivateFn

and commonly use:

inject()

for dependencies.


6. Do functional guards require @Injectable()?

The guard function itself does not require @Injectable().

Its dependencies, such as AuthService, are still provided through Angular's DI system.


7. Can functional guards return an Observable or Promise?

Yes. Functional guards can make synchronous or asynchronous navigation decisions.


Interview-ready answer

Earlier Angular applications commonly implemented route guards as injectable classes that implemented interfaces such as CanActivate, with dependencies provided through constructor injection. Modern Angular supports functional route guards such as CanActivateFn, CanDeactivateFn, and CanMatchFn. Functional guards are simpler and can access dependencies using the inject() function. For an authentication guard, we typically use CanActivateFn to check the authentication state and return true when authenticated or return a redirect to the login page when unauthenticated.