División de código a nivel de la ruta en Angular

Mejora el rendimiento de tu app con la división de código a nivel de ruta.

En esta publicación, se explica cómo configurar la división de código a nivel de la ruta en una aplicación de Angular, lo que puede reducir el tamaño del paquete de JavaScript y mejorar drásticamente el tiempo de carga.

Puedes encontrar las muestras de código de este artículo en GitHub. El ejemplo de enrutamiento inmediato está disponible en la rama Eager. El ejemplo de división de código a nivel de ruta se encuentra en la rama diferida.

Por qué es importante la división de código

La complejidad cada vez mayor de las aplicaciones web ha generado un aumento significativo en la cantidad de JavaScript que se envía a los usuarios. Los archivos JavaScript grandes pueden retrasar notablemente la interactividad, por lo que pueden ser un recurso costoso, especialmente en dispositivos móviles.

La forma más eficiente de reducir los paquetes de JavaScript sin sacrificar las funciones de tus aplicaciones es introducir una división del código agresiva.

La división del código te permite dividir el código JavaScript de tu aplicación en varios fragmentos asociados con diferentes rutas o funciones. Este enfoque solo envía a los usuarios el JavaScript que necesitan durante la carga inicial de la aplicación, lo que mantiene bajos los tiempos de carga.

Técnicas de división de código

La división del código se puede realizar en dos niveles: el nivel del componente y el nivel de la ruta.

  • En la división de código a nivel de los componentes, mueves los componentes a sus propios bloques de JavaScript y los cargas de forma diferida cuando se necesitan.
  • En la división de código a nivel de ruta, encapsulas la funcionalidad de cada ruta en un fragmento separado. Cuando los usuarios navegan por tu aplicación, recuperan los fragmentos asociados con las rutas individuales y obtienen la funcionalidad asociada cuando la necesitan.

Esta publicación se enfoca en la configuración de la división a nivel de la ruta en Angular.

Aplicación de ejemplo

Antes de analizar cómo usar la división del código a nivel de ruta en Angular, veamos una app de ejemplo:

Consulta la implementación de los módulos de la app. Dentro de AppModule, se definen dos rutas: la ruta predeterminada asociada con HomeComponent y una ruta nyan asociada con NyanComponent:

@NgModule({
  ...
  imports: [
    BrowserModule,
    RouterModule.forRoot([
      {
        path: '',
        component: HomeComponent,
        pathMatch: 'full'
      },
      {
        path: 'nyan',
        component: NyanComponent
      }
    ])
  ],
  ...
})
export class AppModule {}

División del código a nivel de la ruta

Para configurar la división de código, se debe refactorizar la ruta inmediata nyan.

La versión 8.1.0 de la CLI de Angular puede hacer todo por ti con este comando:

ng g module nyan --module app --route nyan

Esto generará lo siguiente: - Un nuevo módulo de enrutamiento llamado NyanModule - Una ruta en AppModule llamada nyan, que cargará dinámicamente NyanModule - Una ruta predeterminada en NyanModule - Un componente llamado NyanComponent que se renderizará cuando el usuario seleccione la ruta predeterminada

Veamos estos pasos de forma manual para comprender mejor la implementación de la división de código con Angular.

Cuando el usuario navegue a la ruta nyan, el router renderizará NyanComponent en el tomacorriente.

Para usar la división de código a nivel de ruta en Angular, configura la propiedad loadChildren de la declaración de ruta y combínala con una importación dinámica:

{
  path: 'nyan',
  loadChildren: () => import('./nyan/nyan.module').then(m => m.NyanModule)
}

Existen dos diferencias clave con respecto a la ruta inmediata:

  1. Configuraste loadChildren en lugar de component. Cuando se usa la división de código a nivel de ruta, debes apuntar a módulos cargados de forma dinámica, en lugar de componentes.
  2. En loadChildren, una vez que se resuelve la promesa, se muestra NyanModule en lugar de apuntar a NyanComponent.

En el fragmento anterior, se especifica que, cuando el usuario navega a nyan, Angular debe cargar nyan.module de forma dinámica desde el directorio nyan y renderizar el componente asociado con la ruta predeterminada declarada en el módulo.

Puedes asociar la ruta predeterminada con un componente mediante esta declaración:

import { NgModule } from '@angular/core';
import { NyanComponent } from './nyan.component';
import { RouterModule } from '@angular/router';

@NgModule({
  declarations: [NyanComponent],
  imports: [
    RouterModule.forChild([{
      path: '',
      pathMatch: 'full',
      component: NyanComponent
    }])
  ]
})
export class NyanModule {}

Este código renderiza NyanComponent cuando el usuario navega a https://example.com/nyan.

Para comprobar que el router de Angular descargue la nyan.module de forma diferida en tu entorno local, haz lo siguiente:

  1. Presiona "Control + Mayús + J" (o bien "Comando + Opción + J" en Mac) para abrir Herramientas para desarrolladores.
  2. Haga clic en la pestaña Red.

  3. Haz clic en NYAN en la app de ejemplo.

  4. Ten en cuenta que el archivo nyan-nyan-module.js aparece en la pestaña de red.

Carga diferida de paquetes de JavaScript con división de código a nivel de ruta

Encuentra este ejemplo en GitHub.

Mostrar un ícono giratorio

Por el momento, cuando el usuario hace clic en el botón NYAN, la aplicación no indica que está cargando JavaScript en segundo plano. Para enviar comentarios al usuario mientras cargas la secuencia de comandos, es probable que te convenga agregar un ícono giratorio.

Para ello, comienza por agregar lenguaje de marcado para el indicador dentro del elemento router-outlet en app.component.html:

<router-outlet>
  <span class="loader" *ngIf="loading"></span>
</router-outlet>

Luego, agrega una clase AppComponent para controlar los eventos de enrutamiento. Esta clase establecerá la marca loading en true cuando escuche el evento RouteConfigLoadStart y la establecerá en false cuando escuche el evento RouteConfigLoadEnd.

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  loading: boolean;
  constructor(router: Router) {
    this.loading = false;
    router.events.subscribe(
      (event: RouterEvent): void => {
        if (event instanceof NavigationStart) {
          this.loading = true;
        } else if (event instanceof NavigationEnd) {
          this.loading = false;
        }
      }
    );
  }
}

En el siguiente ejemplo, presentamos una latencia artificial de 500 ms para que puedas ver el ícono giratorio en acción.

Conclusión

Puedes reducir el tamaño del paquete de tus aplicaciones de Angular aplicando la división de código a nivel de ruta:

  1. Usa el generador de módulos de carga diferida de la CLI de Angular para andar automáticamente a una ruta cargada de forma dinámica.
  2. Agrega un indicador de carga cuando el usuario navegue a una ruta diferida para mostrar que hay una acción en curso.