¿En cuántas ocasiones has sufrido porque un sitio web carga demasiado lento? ¿O te ha pasado que si visitas algún enlace no está disponible o simplemente no funciona? La mayoría de las veces esto ocurre porque hay una cantidad excesiva de información que no se ha organizado correctamente o porque el bundle de la aplicación está demasiado cargado. El bundle sirve para empaquetar de forma rápida un conjunto de clases o funciones en Javascript y convertirlas en una aplicación, pero cuando se mandan a llamar todos los métodos a la vez se sobrecarga y se genera una carga lenta de información; es por lo que se debe ocupar la carga perezosa o lazy loads. De esta manera, únicamente realizaremos llamadas a los módulos o componentes que realmente ocupamos y lo podemos realizar por medio del enrutamiento. En este tema aprenderás todo lo necesario para optimizar tus aplicaciones y que a medida que incrementen su tamaño y funcionalidades, no decrementen la velocidad de procesamiento, sino al contrario, sean optimizadas y mejoradas.
Enrutamiento
Angular (s.f.-a) nos dice que es un framework de diseño de aplicaciones y plataforma de desarrollo para crear aplicaciones de una sola página eficientes y sofisticadas. De tal modo que, si se trata de una sola página, ¿cómo hacer para que cuando se requiere navegar en un sitio web entre distintas páginas?, ¿has notado que va cambiando la URL del navegador a medida que cambias de página? A esto se le llama enrutamiento y ocupas rutas. Las rutas sirven para mostrar diferentes componentes basados en el URL del navegador web. Angular (s.f.-b), nos indica que para manejar el enrutamiento principal se nombra, por convención de código, como app-rounting-module.ts y se crea en la carpeta src/app. El archivo se genera con el siguiente comando:
ng generate module app-routing --flat --module=app |
Tabla 1. Comando para generar el archivo de enrutamiento principal.
Una vez creado el archivo debes importar el RouterModule y Routes de @angular/router, y crear un arreglo con las rutas de tipo Routes. Goel (2020), menciona que el router de Angular es el que está detrás de todas las funcionalidades que realizamos en el enrutamiento de una vista a otra en una aplicación Angular. Activa todos los componentes asociados a una determinada configuración de ruta.
Debes tomar en cuenta que es necesario exportar el RouterModule con las rutas para que sean visibles en toda la aplicación. El archivo que se actualizó se vería como el siguiente:
import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { HomeComponent } from './pages/home/home.component';
const routes: Routes = [ { path: 'home', component: HomeComponent } ];
@NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule] }) export class AppRoutingModule { } |
Tabla 2. Ejemplo del archivo principal de enrutamiento.
En el arreglo routes se irán agregando las nuevas rutas con el path y el nombre del componente que desees enlazar. Para poder ocupar el enrutamiento dentro de tu aplicación debes agregar RouterOutlet en el archivo app.component.html, como se muestra en el siguiente ejemplo:
<h1>Mi aplicación con enrutamiento</h1> <router-outlet></router-outlet> |
Tabla 3. Ejemplo de uso de RouterOutlet en archivo app.component.html.
Con lo anterior, cualquier ruta que escribas y que no exista dentro de tu aplicación se eliminará de la barra de direcciones. Esto es porque si no encuentra la ruta, te dirige a la ruta inicial. Angular (s.f.-b), en su documentación, nos explica que el RouterOutlet es una de las directivas del enrutador que estuvo disponible para el AppComponent porque AppModule importa AppRoutingModule que exportó RouterModule.
En caso de que necesites direccionar la página a un componente que tuviera un mensaje de error, por ejemplo, lo tienes que definir en la constante donde se definen las rutas con doble asterisco en el path, como se observa a continuación:
import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { HomeComponent } from './pages/home/home.component'; import { ErrorPageComponent } from './shared/error-page/error-page.component';
const routes: Routes = [ { path: '', component: HomeComponent }, { path: '**', component: ErrorPageComponent, }, ];
@NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule], }) export class AppRoutingModule {} |
Tabla 4. Ejemplo del archivo principal de enrutamiento con direccionamiento a un componente de error.
La forma explícita de agregar en las diferentes rutas lo realizas agregando un nuevo objeto al arreglo routes. De esta forma puedes administrar las rutas de una forma práctica y sencilla. Si tuviéramos más componentes podríamos cargarlos de la siguiente forma:
import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { HomeComponent } from './pages/home/home.component'; import { ErrorPageComponent } from './shared/error-page/error-page.component'; import { FrutasComponent } from './pages/frutas/frutas.component'; import { VerdurasComponent } from './pages/verduras/verduras.component'; import { CerealesComponent } from './pages/cereales/cereales.component';
const routes: Routes = [ { path: '', component: HomeComponent }, { path: '/frutas', component: FrutasComponent }, { path: '/verduras', component: VerdurasComponent }, { path: '/cereales', component: CerealesComponent }, { path: '**', component: ErrorPageComponent, }, ];
@NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule], }) export class AppRoutingModule {} |
Tabla 4. Ejemplo del archivo principal de enrutamiento con direccionamiento explícito.
Ahora, para poder navegar entre las diferentes rutas, se puede hacer por medio de la directiva routerLink. Además, Angular nos ayuda a diferenciar el componente en el que te encuentras a través de la aplicación de un estilo creado por ti mediante la directiva routerLinkActive. Esto lo realizas directamente en el HTML, como se muestra a continuación:
<h1> <a [routerLink]="['']">Mi aplicación con enrutamiento</a> </h1>
<a routerLinkActive="miEstilo" [routerLink]="['frutas']">Frutas</a> <a routerLinkActive="miEstilo" [routerLink]="['verduras']">Verduras</a> <a routerLinkActive="miEstilo" [routerLink]="['cereales']">Cereales</a> <router-outlet></router-outlet> |
Tabla 5. Ejemplo de aplicación de la directiva routerLink.
Carga perezosa (lazy loads)
Angular (s.f.-b) menciona que, por defecto, los NgModules son cargados de manera eagerly. Esto significa que tan pronto como la aplicación cargue, también cargarán todos los NgModules, sean o no inmediatamente necesarios. La carga perezosa o lazy loads te ayuda a reducir notablemente los tiempos de carga de tu aplicación porque reduce el tamaño del bundle inicial y es muy útil en proyectos grandes con varios módulos y rutas.
Para aplicar la carga perezosa es necesario la creación de módulos adicionales donde en cada módulo se creará el archivo de rutas con enrutamiento explicito. Y, en el AppRoutingModule, se crearán rutas hijas en base a los módulos. Cada módulo tendrá sus propios componentes y se deberá crear el archivo de enrutamiento en cada módulo. Para crear el módulo y el archivo de enrutamiento agregando la carga perezosa se realiza con el siguiente comando:
ng generate module ordenes --route ordenes --module app.module |
Tabla 6. Ejemplo de comando para creación de nuevo proyecto.
De esta forma, el archivo de rutas del módulo se creará con enrutamiento explícito y el archivo de enrutamiento principal realizará la carga perezosa, es decir, no se cargará a menos que se requiera. Así se visualiza el código:
import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { OrdenesComponent } from './ordenes.component';
const routes: Routes = [{ path: '', component: OrdenesComponent }];
@NgModule({ imports: [RouterModule.forChild(routes)], exports: [RouterModule] }) export class OrdenesRoutingModule { } |
Tabla 7. Ejemplo de enrutamiento explícito por módulo (ordenes-routing.module.ts).
import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { HomeComponent } from './pages/home/home.component'; import { ErrorPageComponent } from './shared/error-page/error-page.component';
const routes: Routes = [ { path: '', component: HomeComponent }, { path: 'ordenes', loadChildren: () => import('./ordenes/ordenes.module').then((m) => m.OrdenesModule), }, { path: '**', component: ErrorPageComponent, }, ];
@NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule], }) export class AppRoutingModule {} |
Tabla 8. Ejemplo de carga perezosa en enrutamiento principal (app-routing.module.ts).
Como observas en el ejemplo, se cambió la propiedad component por una ruta hija que únicamente se cargará en caso de ser requerida.
Protección de rutas
Angular nos da la posibilidad de proteger cada una de las rutas a las que puede acceder un usuario de tu aplicación. Por ejemplo, si deseas que solo los usuarios de tipo administrador puedan ver ciertas pantallas, pero que los usuarios comunes no las puedan ver, puedes aplicar la protección de rutas por medio de guards.
Los guards en Angular son pequeñas funciones que se ejecutan antes de cargar cualquier ruta de la aplicación. Los principales tipos de guards son los siguientes:
Figura 1. Tipos de guards.
Para crear un guard se puede realizar con el siguiente comando:
ng generate guard miGuard |
Tabla 9. Comando para generar el archivo de enrutamiento principal.
Con el comando anterior, Angular CLI te preguntará: ¿Cuál de los cuatro tipos de Guard deseas implementar? Y, de acuerdo con el que selecciones, te creará diferentes archivos. A continuación, un ejemplo de cómo se genera el tipo con todos los tipos de Guard:
import { Injectable } from '@angular/core'; import { ActivatedRouteSnapshot, CanActivate, CanActivateChild, CanDeactivate, CanLoad, Route, RouterStateSnapshot, UrlSegment, UrlTree } from '@angular/router'; import { Observable } from 'rxjs';
@Injectable({ providedIn: 'root' }) export class GuardsExamplesGuard implements CanActivate, CanActivateChild, CanDeactivate<unknown>, CanLoad { canActivate( route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree { return true; } canActivateChild( childRoute: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree { return true; } canDeactivate( component: unknown, currentRoute: ActivatedRouteSnapshot, currentState: RouterStateSnapshot, nextState?: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree { return true; } canLoad( route: Route, segments: UrlSegment[]): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree { return true; } } |
Tabla 10. Ejemplo de guards.
Los guards son clases que ocupan la interfaz de acuerdo con el tipo de guard que se utilice. Para mostrar el funcionamiento, realizaremos un ejemplo con el canActivate, que es el más usado. Para poder proteger una ruta, debemos de escribir las restricciones que deseamos dentro del método canActivate. En el siguiente ejemplo se genera una constante de tipo boolean que devuelve true o false para permitir el acceso a la ruta:
import { Injectable } from '@angular/core'; import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot, UrlTree, } from '@angular/router'; import { Observable } from 'rxjs';
@Injectable({ providedIn: 'root', }) export class CanActivateExampleGuard implements CanActivate { constructor(private router: Router) {}
canActivate( route: ActivatedRouteSnapshot, state: RouterStateSnapshot ): boolean { const permiso = Boolean(Math.round(Math.random()));
if (!permiso) { console.log('Acceso denegado'); this.router.navigate(['']); return false; // Se restringe el accesso a la ruta y se navega a la página principal. }
console.log('Acceso permitido'); return true; // Se permite el accesso a la ruta. } } |
Tabla 11. Ejemplo de CanActivate.
El guard se puede ocupar en cualquier ruta de tu aplicación, incluso más de una vez. En este caso, se aplicará a la ruta /ordenes.
import { NgModule } from '@angular/core'; import { RouterModule, Routes, CanActivate } from '@angular/router'; import { HomeComponent } from './pages/home/home.component'; import { ErrorPageComponent } from './shared/error-page/error-page.component'; import { CanActivateExampleGuard } from './guards/can-activate-example.guard';
const routes: Routes = [ { path: '', component: HomeComponent }, { path: 'ordenes', loadChildren: () => import('./ordenes/ordenes.module').then((m) => m.OrdenesModule), canActivate: [CanActivateExampleGuard], }, { path: '**', component: ErrorPageComponent, }, ];
@NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule], }) export class AppRoutingModule {} |
Tabla 12. Ejemplo de aplicación en una ruta con CanActivate.
El uso de los guards facilita el manejo de permisos de usuario en una aplicación, por lo que hace más sencilla la implementación de una parte de la seguridad.
El direccionamiento en una aplicación, una parte fundamental en el desarrollo, para lo cual Angular facilita el trabajo del desarrollo de aplicaciones mediante el uso de Routes. A su vez, también aplica la protección a estas rutas a través de la implementación de los guards. Con ayuda de los guards se pueden administrar los accesos a cada una de las rutas; inclusive se puede implementar la protección para cada una de las rutas hijas en la aplicación. Cuando se tiene una correcta administración de las rutas y el control de accesos, se puede implementar la carga por módulos que también es conocida como lazy load o carga perezosa. Como te habrás dado cuenta, Angular contribuye a una mejor organización de cada uno de los elementos se desarrollan, proporcionando las herramientas para realizar un código más limpio y sencillo con la potencia que requiere una aplicación web. Con lo que has aprendido, ¿cómo lo usarías en el desarrollo de una aplicación web?
Asegúrate de: