Guards en Angular: Protección y Control de Navegación en tu Aplicación

Descubre cómo usar los guards en Angular para controlar la navegación y proteger rutas en tus aplicaciones. Aprende con ejemplos prácticos y mejora la seguridad de tu aplicación Angular.

Guards en Angular: Protección y Control de Navegación en tu Aplicación

Los guards en Angular son una herramienta esencial para controlar la navegación y proteger rutas en tus aplicaciones. En este artículo, te proporcionaremos una guía completa sobre los diferentes tipos de guards disponibles en Angular, cómo implementarlos y cuándo usarlos para garantizar la seguridad y una experiencia de usuario fluida.

Tabla de Contenidos

¿Qué son los Guards en Angular?

En Angular, los guards son servicios que permiten controlar el acceso a las rutas en tu aplicación. Son una forma de definir la lógica que determina si una ruta puede ser activada, desactivada o cargada. Esto te ayuda a proteger rutas y manejar la navegación de los usuarios en tu aplicación de manera más eficiente.

Tipos de Guards

CanActivate

El guard CanActivate se utiliza para decidir si una ruta puede ser activada. Esto es útil para verificar, por ejemplo, si un usuario está autenticado antes de permitirle acceder a una ruta específica.

Ejemplo Real:

Imagina que tienes una aplicación con una ruta para el perfil de usuario que solo debe ser accesible si el usuario está autenticado. Aquí está cómo implementar CanActivate:

import { Injectable } from '@angular/core';
import { CanActivate, Router } from '@angular/router';
import { AuthService } from './auth.service';

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

  canActivate(): boolean {
    if (this.authService.isAuthenticated()) {
      return true;
    } else {
      this.router.navigate(['/login']);
      return false;
    }
  }
}

Uso en el enrutador (router):

const routes: Routes = [
  {
    path: 'profile',
    component: ProfileComponent,
    canActivate: [AuthGuard]
  }
];

CanDeactivate

El guard CanDeactivate se usa para prevenir la navegación fuera de una ruta si hay cambios no guardados. Es ideal para formularios o componentes donde el usuario podría perder datos si navega a otra página.

Ejemplo Real:

Supón que tienes un componente de formulario y quieres asegurarte de que el usuario confirme antes de navegar fuera si hay cambios no guardados:

import { Injectable } from '@angular/core';
import { CanDeactivate } from '@angular/router';
import { Observable } from 'rxjs';

export interface CanComponentDeactivate {
  canDeactivate: () => Observable<boolean> | Promise<boolean> | boolean;
}

@Injectable({
  providedIn: 'root'
})
export class UnsavedChangesGuard implements CanDeactivate<CanComponentDeactivate> {
  canDeactivate(component: CanComponentDeactivate): Observable<boolean> | Promise<boolean> | boolean {
    return component.canDeactivate ? component.canDeactivate() : true;
  }
}

Uso en el componente:

import { Component } from '@angular/core';
import { CanComponentDeactivate } from './unsaved-changes.guard';

@Component({
  selector: 'app-edit-profile',
  templateUrl: './edit-profile.component.html'
})
export class EditProfileComponent implements CanComponentDeactivate {
  hasUnsavedChanges = true;

  canDeactivate(): boolean {
    if (this.hasUnsavedChanges) {
      return confirm('You have unsaved changes. Do you really want to leave?');
    }
    return true;
  }
}

Uso en el enrutador:

const routes: Routes = [
  {
    path: 'edit-profile',
    component: EditProfileComponent,
    canDeactivate: [UnsavedChangesGuard]
  }
];

CanLoad

El guard CanLoad se utiliza para prevenir la carga de módulos perezosos (lazy-loaded modules) si no se cumplen ciertas condiciones. Es útil para asegurar que los módulos sólo se carguen cuando el usuario tenga permiso.

Ejemplo Real:

Supongamos que tienes un módulo de administración que solo debe cargarse si el usuario es un administrador:

import { Injectable } from '@angular/core';
import { CanLoad, Route, UrlSegment, Router } from '@angular/router';
import { AuthService } from './auth.service';

@Injectable({
  providedIn: 'root'
})
export class AdminLoadGuard implements CanLoad {
  constructor(private authService: AuthService, private router: Router) {}

  canLoad(route: Route, segments: UrlSegment[]): boolean {
    if (this.authService.isAdmin()) {
      return true;
    } else {
      this.router.navigate(['/no-access']);
      return false;
    }
  }
}

Uso en el enrutador:

const routes: Routes = [
  {
    path: 'admin',
    loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule),
    canLoad: [AdminLoadGuard]
  }
];

Resolve

El guard Resolve se usa para pre-cargar datos antes de activar una ruta. Esto asegura que los datos necesarios estén disponibles antes de que el componente asociado con la ruta se cargue.

Ejemplo Real:

Imagina que tienes una página de detalles de producto y quieres cargar la información del producto antes de que se muestre la página:

import { Injectable } from '@angular/core';
import { Resolve, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { Observable, of } from 'rxjs';
import { ProductService } from './product.service';
import { catchError } from 'rxjs/operators';

@Injectable({
  providedIn: 'root'
})
export class ProductResolver implements Resolve<any> {
  constructor(private productService: ProductService) {}

  resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<any> {
    const productId = route.paramMap.get('id');
    return this.productService.getProduct(productId).pipe(
      catchError(error => {
        console.error('Error loading product', error);
        return of(null);
      })
    );
  }
}

Uso en el componente:

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';

@Component({
  selector: 'app-product-detail',
  templateUrl: './product-detail.component.html'
})
export class ProductDetailComponent implements OnInit {
  product: any;

  constructor(private route: ActivatedRoute) {}

  ngOnInit(): void {
    this.route.data.subscribe(data => {
      this.product = data.product;
    });
  }
}

Uso en el enrutador:

const routes: Routes = [
  {
    path: 'product/:id',
    component: ProductDetailComponent,
    resolve: { product: ProductResolver }
  }
];

Cómo Implementar un Guard

Para implementar un guard, debes seguir estos pasos:

  1. Crear el Guard: Utiliza el CLI de Angular para generar un guard.
ng generate guard auth
  1. Implementar la Lógica: Define la lógica específica en el guard para controlar la navegación.

  2. Registrar el Guard: Añade el guard a las rutas en tu módulo de enrutamiento.

const routes: Routes = [
  {
    path: 'dashboard',
    component: DashboardComponent,
    canActivate: [AuthGuard]
  },
  {
    path: 'edit',
    component: EditComponent,
    canDeactivate: [UnsavedChangesGuard]
  }
];

Ejemplos Reales

Ejemplo Completo de una Aplicación

Supongamos que estás construyendo una aplicación de comercio electrónico con las siguientes características:

  1. Protección de Rutas: Solo los usuarios autenticados pueden acceder a su perfil y a la administración.
  2. Prevención de Pérdida de Datos: Los usuarios deben confirmar antes de abandonar una página de edición de perfil si hay cambios no guardados.
  3. Carga Condicional de Módulos: Los módulos de administración solo se cargan si el usuario tiene permisos de administrador.
  4. Pre-carga de Datos: La información del producto se carga antes de mostrar los detalles del producto.

Enrutador Configurado:

const routes: Routes = [
  {
    path: 'profile',
    component: ProfileComponent,
    canActivate: [AuthGuard]
  },
  {
    path: 'edit-profile',
    component: EditProfileComponent,
    canDeactivate: [UnsavedChangesGuard]
  },
  {
    path: 'admin',
    loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule),
    canLoad: [AdminLoadGuard]
  },
  {
    path: 'product/:id',
    component: ProductDetailComponent,
    resolve: { product: ProductResolver }
  },
  {
    path: 'login',
    component: LoginComponent
  },
  {
    path: 'no-access',
    component: NoAccessComponent
  }
];

Conclusión

Los guards en Angular son fundamentales para controlar el acceso y la navegación en aplicaciones complejas. Usando CanActivate, CanDeactivate, CanLoad y Resolve, puedes manejar de manera efectiva la seguridad y la experiencia del usuario en tu aplicación. Los ejemplos proporcionados muestran cómo puedes aplicar estos guards en situaciones prácticas para mejorar la robustez de tu aplicación.