Angular Functional Route Guards - Everything You Need To Know


In Angular vs 14 functional route guards were introduced as a new feature.

The new function-style route guard feature was created to replace the old class-based style of writing route guards in a Typescript class.

funny-monkey

And the Angular team was even taken steps to completely deprecate the class-based route guards until the community let out a cry in the GitHub issues begging for continued support of the old style.

As for now, the old style is still supported. But when we use the Angular CLI to generate a route guard it will use the new function route guard specs.

What do the old class-style route guards look like?

First, let's start by taking a look at the class based route guards that are still supported.

Basically, it was an injectable class that could implement two functions - CanActivate and CanActivateChild.

These functions returned either a boolean value or a route for the guard to route the user to.

Here's a simple example that checks for a token in local storage. If it doesn't exist we route the user to a login page.

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

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

  constructor(private router: Router) {}

  canActivate(
    next: ActivatedRouteSnapshot,
    state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
      const token = localStorage.getItem("token");
      if (token) {
          return true;
      }
      return this.router.parseUrl("/auth/login");
  }

  canActivateChild(
    childRoute: ActivatedRouteSnapshot,
    state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
      const token = localStorage.getItem("token");
      if (token) {
          return true;
      }
      return this.router.parseUrl("/auth/login");
  }
}

But that all changed with the new functional router guards that were introduced with Angular vs 14.

An example of the Angular functional router guard

Now, the router guard can be implemented as a simple function like this.

import { ActivatedRouteSnapshot, CanActivateFn, RouterStateSnapshot } from '@angular/router';

export const appGuard: CanActivateFn = (route: ActivatedRouteSnapshot, state: RouterStateSnapshot) => {
  const token = localStorage.getItem("token");
  if (token) {
    return true;
  }
  return false;
};

You can see that this nothing more then a function that takes two parameters - route and state - and then it can return either a boolean or a UrlTree.

How do you inject dependencies into the new function-style Angular guard?

One of the most common problems with the new function style route guard is that since this is a function there is no constructor that allows you to inject dependencies.

For example, we might need the Router dependency to do some route parsing. Instead of using a constructor, we'll use the new inject function in Angular.

So to inject a dependency in the new function style Angular guard it will look like this.

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

export const appGuard: CanActivateFn = (route: ActivatedRouteSnapshot, state: RouterStateSnapshot) => {
  const router = inject(Router);
  const token = localStorage.getItem("token");
  if (token) {
    return true;
  }
  return router.parseUrl("/auth/login");
};

Conclusion

And that, my Angular friend, is everything you need to know about the new function route guards in Angular.

Personally, I think this is a great improvement over the class-based styles we used to use in the early days of Angular.

What do you think?

Till next time,

signature

Daniel Kreider - Angular Developer