Binary Coffee

Guards en Angular, 驴C贸mo funcionan?

angular nodejs typescript

Que es un Guard

Los Guards en Angular, son de alguna manera: middlewares que se ejecutan antes de cargar una ruta y determinan si se puede cargar dicha ruta o no. Existen 4 tipos diferentes de Guards (o combinaciones de estos) que son los siguientes:

  1. (CanActivate) Antes de cargar los componentes de la ruta.
  2. (CanLoad) Antes de cargar los recursos (assets) de la ruta.
  3. (CanDeactivate) Antes de intentar salir de la ruta actual (usualmente utilizado para evitar salir de una ruta, si no se han guardado los datos).
  4. (CanActivateChild) Antes de cargar las rutas hijas de la ruta actual.

Como middleware, estos componentes se ejecutan de manera intermedia antes de determinadas acciones y si retorna true la ruta seguir铆a su carga normal, en caso negativo, el Guard retornar铆a false y la ruta no se cargar铆a. Generalmente en caso de que no se cumpla la condici贸n del Guard, se suele hacer una redirecci贸n a la ruta anterior o a una ruta definida como la interfaz de autenticaci贸n.

驴Por qu茅 utilizar Guards?

Una de las ventajas de utilizarlos, es que al no cargar la ruta evitamos que los usuarios vean una interfaz a la que no tienen acceso (aunque sea por unos pocos milisegundos). Por otra parte, con estos componentes es posible estructurar el c贸digo de la aplicaci贸n de una manera m谩s organizada y donde la l贸gica de la ruta est谩 en la ruta en s铆 y la l贸gica de permisos y acceso a recursos se encuentra aislada en estos componentes.

驴C贸mo utiliza Angular los Guards?

Empezaremos con la creaci贸n de un Guard. Para crear un Guard es posible hacerlo de manera manual, pero angular-cli provee un generador muy bueno y que de manera autom谩tica genera el Guard junto a las pruebas del mismo. Para generar el Guard solo es necesario abrir un terminal en la carpeta que se desea y ejecutar el siguiente comando:

$ ng generate guard <guard-name>

Una vez ejecutado el comando anterior, dan a elegir cu谩l de los 4 tipos de Guard deseas generar y una vez seleccionado, este es creado junto a su archivo de pruebas unitarias. A continuaci贸n un ejemplo del Guard generado:

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

import {Observable} from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class AuthGuard implements CanActivate {
  canActivate(
    next: ActivatedRouteSnapshot,
    state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
    return true;
  }
}

Si tenemos en cuenta la l贸gica que esperamos del nuevo Guard creado, podr铆amos tener algo como el siguiente ejemplo, donde comprobamos que exista una session abierta y en caso contrario, redireccionamos a la ruta de autenticaci贸n.

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

import {Observable} from 'rxjs';

import {AuthService} from './auth.service';

@Injectable({
  providedIn: 'root'
})
export class AuthGuard implements CanActivate {
  constructor(private auth: AuthService, private router: Router) {
  }

  canActivate(
    next: ActivatedRouteSnapshot,
    state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
    if (!this.auth.isLogin()) {
      return this.router.navigate(['/login']).then(() => false);
    }
    return true;
  }
}

Utilizando los Guards

Para utilizar un Guard es bien sencillo, basta con ir a la definici贸n de las rutas de la aplicaci贸n, y agregar el siguiente c贸digo.

const routes: Routes = [
  {
    path: '',
    canActivate: [AuthGuard],
    component: AppComponent
  },
  {
    path: 'login',
    component: LoginComponent
  }
];

Como se puede apreciar en el ejemplo anterior, seg煤n el tipo de Guard, es en la propiedad en que este debe ser agregado. Y como dije anteriormente, perfectamente pueden ser combinadas m谩s de una acci贸n en un solo Guard, dado que bastar铆a con implementar dicha acci贸n y luego agregarlo en la ruta.

Analizando los Guard como servicios

Si miramos m谩s de cerca los Guard, podremos ver que efectivamente, como todos los servicios, estos tienen el decorador @Injectable. Esto quiere decir, que los Guard deben agregarse en un M贸dulo de la aplicaci贸n en la secci贸n de providers, o si se utiliza con el par谩metro providedIn:'root' con el que son generados por defecto, esto no es necesario, puesto que significa que este Guard est谩 disponible para toda la aplicaci贸n.

Por cuestiones de eficiencia, esta propiedad de ser globales no la recomiendo, puesto que incluso en aquellos m贸dulos en que la aplicaci贸n no los utilice, estos van a ser cargados.

驴C贸mo se ejecutan los Guard en Angular?

Esta es una parte bien importante, que recientemente me tom贸 varias horas de investigaci贸n y rompeduras de cabeza, sobre c贸mo el Core de Angular trata a estos componentes. Pues resulta que no importa el orden en que se pongan los Guard:

const routes: Routes = [
  ...
  {
    path: '',
    canActivate: [FirstGuard, SecondGuard],
    component: AppComponent
  },
  ...
];

la acci贸n de los Guards siempre es ejecutada, y por tanto, los Guard no son excluyentes, es decir: que el hecho de que el primer Guard se ejecute y retorne falso, no significa que el segundo Guard no va a ser ejecutado.

Y te preguntar谩s 驴y para qu茅 me sirve esto? Pues resulta que en mi experiencia si me import贸, porque si por ejemplo, tenemos un Guard que toma datos de la url, o algo semejante para crear o definir una acci贸n que puede incluso excluir al segundo Guard, puede verse completamente destrozado, porque luego llega y se ejecuta el segundo Guard y PUMM, se rompe todo.

Luego de investigar un poco y ver las posibles soluciones para este caso de uso en particular, encontr茅 lo que creo ser铆a la soluci贸n al problema, y no es m谩s que crear nuestro Guard intermedio, que ejecutar铆a a los Guards de la ruta. Este nuevo Guard que se crear谩, tendr谩 la responsabilidad de ejecutarlos solo si al ejecutar el anterior, la respuesta fue afirmativa. A continuaci贸n un ejemplo de como quedar铆a este Guard intermedio y de como usarlo si en alg煤n momento chocaras con el mismo problema.

@Injectable({
  providedIn: 'root'
})
export class IntermediateGuard implements CanActivate {

  constructor(protected injector: Injector) {
  }

  canActivate(
    next: ActivatedRouteSnapshot,
    state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {

    let compositeCanActivateObservable: Observable<boolean> = of(true);
    const routeGuards = next.data.routeGuards;

    if (routeGuards) {
      for (let i = 0; i < routeGuards.length; i++) {
        const routeGuard = this.injector.get(routeGuards[i]);
        compositeCanActivateObservable = compositeCanActivateObservable.pipe(flatMap((bool) => {
          if (!bool) {
            return of(false);
          } else {
            return routeGuard.canActivate(next, state) as Observable<boolean>;
          }
        }));
      }
    }

    return compositeCanActivateObservable;
  }
}

y para utilizar dicho Guard, la ruta quedar铆a de la siguiente manera:

{
  ...
  path: '',
  canActivate: [IntermediateGuard],
  data: { routeGuards: [FirstGuard, SecondGuard] }
  ...
}

Como se puede apreciar, en vez de agregar los Guards de la ruta a la propiedad canActivate, estos se agregan a los datos de la ruta, para luego ser aplicados por el Guard intermedio.

Conclusiones

Espero te halla sido de utilidad el art铆culo y aprendieras algo si es la primera vez que lees sobre el tema. Y ya sabes: deja tus comentarios si te ayud贸 o si simplemente crees que se podr铆a agregar algo m谩s.

Happy coding!

Opiniones