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