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-availablecomponent. - 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
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 {
}
Now, associate the component with a route in the router configuration.
{
path: 'no-available',
loadComponent: () => import('./pages/noavailable/noavailable.component').then(m => m.NoAvailableComponent)
}
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) )
)
}
}
Getting Familiar with Guards
Traditional Class Guards are essentially services that implement specific interfaces tied to router events. For instance:
CanMatchGuardresponds to thenavigationStartevent.- Route loading is guarded by
CanLoadGuard. CanActivateChildGuardmanages 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)
)
}
}
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),
},
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),
},
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
}
))
}
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),
},
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


