How To Use Functional Router Guards in Angular

Dany ParedesDany Paredes
4 min read

Today I was talking with my friend Leifer, and he asked me some about Functional Guards in Angular (14/15) with some questions.

  • How do functional Router Guards work?

  • Can The Class guards continue working in Standalone Components?

  • How hard is converting class guards to functional?

  • How to Inject dependencies for my Functional Guard?

The article answers all these questions, but I kept it short using the same Angular Standalone Components project.

The Scenario

We want to protect the path 'domains'. If one service return false, redirect the users to the 'no-available' page, following the following steps:

  • Create the component 'no-available'

  • Create a service to provide the domain status.

  • Create Class Guard with the service and router injected to redirect the user to the page 'no-available.

  • Register the Class Guard in my router with Standalone Components

  • Convert Class Guard to Functional Guards.

Component And Service

If you read my article about standalone components, from Angular 14, we can create standalone components with the flag --standalone.

ng g c pages/available --standalone

In the component, add the message, and the final code looks this:

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

@Component({
  standalone: true,
  selector: 'no-available',
  template: `<h2>Sorry Domain is not available anymore :(</h2>`
})

export class NoAvailableComponent  {
}

Next, register the component in the router:

  {
    path: 'no-available',
    loadComponent: () => import('./pages/noavailable/noavailable.component').then(m => m.NoAvailableComponent)
  }

The Service

We need a service to use in the guard; let's create the DomainService with an isAvailable method that returns an observable with a false value. This false value indicates that the domain is not available.

import {Injectable} from '@angular/core';
import {of, tap} from 'rxjs';

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

  isAvailable() {
    return of(false).pipe(
      tap((v) =>console.log(v) )
    )
  }
}

Before Starting with Guards

The Class Guards are services implementing interfaces linked with a few router events, for example:

  • navigationStart : CanMatchGuard

  • CanLoadRoute: CanLoadGuard

  • ChildRouteActivation: CanActivateChildGuard

  • RouterActivation: canActivateGuard

If you never play with guards, I recommend the example in the official Angular docs.

Class Guards

The Guards are services implementing interfaces like CanActivate, and it continues working with Standalone components.

Create the guard DomainGuard, implements the canActivate interface, and inject the router and the domainService, in the constructor.

The canActivate use the method isAvailable from the service when the return is false, then use the router to navigate to the no-available route.

import {Injectable} from '@angular/core';
import {ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot} from '@angular/router';
import {tap} from 'rxjs';
import {DomainService} from './domain.service';

@Injectable({providedIn: 'root'})
export class DomainsGuard implements CanActivate {
  constructor(private domainService: DomainService, private router: Router) {

  }

  canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
     return this.domainService.isAvailable().pipe(
       tap(value =>  !value ?  this.router.navigate(['/no-available']) : true)
     )
  }
}

Using Class Guards With Standalone Components

Our next step is to register the guard with the standalone component. Open the routes.ts file, and use the canActivate property in the domain path. Add the DomainGuard class and test your code.

  {
    path: 'domains',
    canActivate: [DomainsGuard],
    loadComponent: () => import('./pages/domains/domains.component').then(m => m.DomainsComponent),
  },

noaccess.gif

Turn to Functional Guards

Ok, our guards work, but how can we turn on o functional guards? The canActivate property array accepts functions so that we can write an arrow function and return the false into him, something like:

 {
    path: 'domains',
    canActivate: [() => false],
    loadComponent: () => import('./pages/domains/domains.component').then(m => m.DomainsComponent),
  },

The function () => false, denies the access to the 'domain' route.

noaccess-function.gif

Using Inject()

In Angular 14, we can use the inject function in the constructor function scope to inject external dependencies in our functions.

Our guard functions need to get the router and the domain service to match our guards requirements.

If you want to learn more about the inject I recommend looking at the Article of Vlad about Inject to answer your questions.

import {inject} from '@angular/core';
import {Router} from '@angular/router';
import {tap} from 'rxjs';
import {DomainService} from '../domain.service';

export const domainGuard = () => {
    const router = inject(Router);
    const service = inject(DomainService)
    return service.isAvailable().pipe(
    tap((value) => {
      return !value ? router.navigate(['/no-available']) : true
    }
  ))
}

Next, register in the router, as we did before with the class.

 {
    path: 'domains',
    canActivate: [domainGuard],
    loadComponent: () => import('./pages/domains/domains.component').then(m => m.DomainsComponent),
  },

Yeah!! We have our functional guards with standalone components.

Conclusion

We learn how to use Class Guard in Standalone mode, as well as how to convert it to a functional guard. We also use inject to provide the necessary dependencies for the functional guard.

Have you tried using functional guards? Please share your thoughts in the comments below.

The full code in GitHub

3
Subscribe to my newsletter

Read articles from Dany Paredes directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Dany Paredes
Dany Paredes

I'm passionate about front-end development, specializing in building UI libraries and working with technologies like Angular, NgRx, Accessibility, and micro-frontends. In my free time, I enjoy writing content for the Google Dev Library, This Is Angular Community, Kendo UI, and sharing insights here.