🇺🇸 EN
Frontend
·5 min read·↗·🇺🇸 EN

Cómo usar Functional Router Guards en Angular

Portada principal del artículo: Cómo usar Functional Router Guards en Angular

Hoy estuve conversando con mi amigo Leifer y me hizo varias preguntas sobre los Functional Guards en Angular (14/15):

  • ¿Cómo funcionan los functional router guards?
  • ¿Los guards basados en clases siguen funcionando con Standalone Components?
  • ¿Qué tan difícil es convertir un guard de clase a uno funcional?
  • ¿Cómo inyecto dependencias en mi functional guard?

En este artículo respondo a todas estas preguntas de forma breve y práctica, utilizando el mismo proyecto con Angular Standalone Components.

El escenario

Queremos proteger la ruta 'domains'. Si un servicio retorna false, redirigimos a los usuarios a la página 'no-available', siguiendo estos pasos:

  • Crear el componente 'no-available'.
  • Crear un servicio que provea el estado del dominio.
  • Crear un Class Guard con el servicio y el router inyectados para redirigir al usuario a la página 'no-available'.
  • Registrar el Class Guard en nuestro router con Standalone Components.
  • Convertir el Class Guard a Functional Guards.

Para comenzar con este flujo, primero necesitamos preparar el componente y el servicio.

Componente y servicio

Si leíste mi artículo sobre standalone components, sabes que a partir de Angular 14 podemos crear componentes standalone con la bandera --standalone.

ng g c pages/available --standalone

En el componente agregamos el mensaje para el usuario. El código final queda así:

import {Component} from '@angular/core';
 
@Component({
  standalone: true,
  selector: 'no-available',
  template: `<h2>Sorry Domain is not available anymore :(</h2>`
})
 
export class NoAvailableComponent  {
}

El siguiente paso es registrar el componente en el router:

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

El servicio

Necesitamos un servicio para usar dentro del guard. Vamos a crear DomainService con el método isAvailable, el cual retorna un observable con el valor false. Este valor indica que el dominio no está disponible.

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

Con el componente y el servicio listos, repasemos brevemente cómo funcionan los guards tradicionales antes de escribir la lógica.

Antes de comenzar con los guards

Los Class Guards son servicios que implementan interfaces vinculadas a eventos del router, por ejemplo:

  • navigationStart: CanMatchGuard
  • CanLoadRoute: CanLoadGuard
  • ChildRouteActivation: CanActivateChildGuard
  • RouterActivation: canActivateGuard

Si nunca has trabajado con guards, te recomiendo revisar el ejemplo en la documentación oficial de Angular.

Ahora veamos cómo implementar nuestro guard utilizando una clase tradicional.

Guards basados en clases

Los guards tradicionales son servicios que implementan interfaces como CanActivate, y siguen funcionando perfectamente con Standalone Components.

Creamos el guard DomainsGuard, implementamos la interfaz CanActivate e inyectamos router y domainService en el constructor.

El método canActivate utiliza el método isAvailable del servicio. Cuando el retorno es false, utiliza el router para navegar hacia la ruta no-available:

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

Una vez creado el guard, el siguiente paso es registrarlo en nuestras rutas.

Uso de Class Guards con Standalone Components

Nuestro siguiente paso es registrar el guard con el componente standalone. Abre el archivo routes.ts y utiliza la propiedad canActivate en la ruta domains. Agrega la clase DomainsGuard y prueba tu código:

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

La protección funciona correctamente, pero veamos cómo podemos simplificar todo esto convirtiéndolo en un guard funcional.

Paso a Functional Guards

Nuestro guard funciona, pero ¿cómo pasamos a los functional guards? El arreglo de la propiedad canActivate acepta funciones, por lo que podemos escribir una función flecha y retornar false directamente:

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

La función () => false deniega el acceso a la ruta 'domains':

noaccess-function.gif
noaccess-function.gif

Ahora bien, un guard real necesita dependencias como servicios o el router. Veamos cómo resolver esto con funciones.

Uso de inject()

A partir de Angular 14 podemos usar la función inject() dentro del contexto de inyección para obtener dependencias externas en nuestras funciones.

Nuestra función guard necesita obtener el Router y el DomainService para cumplir con los requerimientos que definimos.

Si quieres aprender más sobre inject(), te recomiendo revisar el artículo de Vlad sobre el tema.

Con esto en mente, así queda nuestro guard funcional:

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

Luego lo registramos en el router, igual que hicimos antes con la clase:

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

¡Listo! Ya tenemos nuestros functional guards funcionando con Standalone Components.

Conclusión

Aprendimos cómo usar un Class Guard con Standalone Components y cómo convertirlo a un functional guard mucho más conciso. También vimos cómo usar inject() para proveer las dependencias necesarias sin constructores.

¿Has probado los functional guards en tus proyectos? Comparte tu experiencia en los comentarios.

Puedes encontrar el código completo en GitHub.

Parte de la serie Frontend

Modern frontend development — Angular, React, TypeScript, accessibility, and performance.

Ver serie completa

Artículos Relacionados

Compartir este artículo

Si esta guía te resultó útil, compártela con tu equipo o con otros desarrolladores.


Software real. Lecciones reales.

Comparto las lecciones que aprendí por las malas, para que puedas evitarlas o estar preparado cuando ocurran.

User avatar
User avatar
User avatar
User avatar
+13K

Únete a 13,800+ desarrolladores y lectores.

Cero spam. Cancela tu suscripción en cualquier momento.