The Use Case

Let's consider a scenario where the 'domains' path needs protection. If the service indicates the domain is not available, the user should be redirected to the 'no-available' page. Here’s the plan:

  • Build the no-available component.
  • Set up a service to report the domain's availability.
  • Implement a Class Guard that injects the service and router to manage redirection.
  • Apply the Class Guard within a Standalone Components setup.
  • Refactor the Class Guard into a Functional Guard.

Component and Service Setup

As mentioned in my previous piece on standalone components, Angular 14 introduced the ability to create these components using the --standalone flag.

ng g c pages/available --standalone
Enter fullscreen mode Exit fullscreen mode

Add a message to the component to complete its implementation.

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

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

export class NoAvailableComponent  {
}

Enter fullscreen mode Exit fullscreen mode

Now, associate the component with a route in the router configuration.

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

The Service

A service is required for the guards to use. Let's create a DomainService that exposes an isAvailable method, which returns an observable that emits a boolean true.

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) )
    )
  }
}

Enter fullscreen mode Exit fullscreen mode

Getting Familiar with Guards

Traditional Class Guards are essentially services that implement specific interfaces tied to router events. For instance:

  • CanMatchGuard responds to the navigationStart event.
  • Route loading is guarded by CanLoadGuard.
  • CanActivateChildGuard manages child route activation.
  • The main route activation is handled by canActivateGuard.

If you're new to guards, the official Angular documentation provides a solid introductory tutorial.

Working with Class Guards

Guards that implement interfaces like CanActivate remain fully compatible with Standalone components.

Let's create a DomainGuard that implements the canActivate interface. We'll inject the router and the domainService into its constructor.

Inside the canActivate method, we'll call the isAvailable method from the service. If it returns false, the guard will use the injected router to navigate users 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)
     )
  }
}

Enter fullscreen mode Exit fullscreen mode

Integrating Class Guards with Standalone Components

With the guard ready, the next step is to register it. Open your routes.ts file and apply the canActivate property to the domains path, adding the DomainGuard class to the array. You can then test the setup.

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

one

Transitioning to Functional Guards

The current guards function correctly, but how do we switch to a functional approach? Since the canActivate array accepts functions, we can directly insert an arrow function into it.

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

two

Leveraging the inject() Function

Angular 14 introduced the inject function, which can be used within a function's scope to pull in external dependencies.

To match our previous requirements, the functional guard needs access to the router and the domain service.

For a deeper dive into the inject function, I recommend checking out this detailed article by @armen: Always use inject.

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
    }
  ))
}
Enter fullscreen mode Exit fullscreen mode

After defining the function, we register it in the router configuration, just as we did with the class-based approach.

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

three And there you have it—our functional guards are now operational with standalone components.

Wrapping Up

You’ve seen how to apply a class-based guard within a standalone setup and then migrate it to a functional guard. The inject function makes it straightforward to supply any required dependencies directly inside the functional guard.

Are functional guards your preferred approach? Feel free to drop a comment or share your thoughts.

You can find the complete source code on GitHub.

Photo credit: Praveesh Palakeel on Unsplash