Lazy loading

Avanzado
Angular
Angular
Actualizado: 24/09/2025

Lazy loading con loadComponent y loadChildren

El lazy loading en Angular permite cargar código bajo demanda, reduciendo el tamaño inicial del bundle y mejorando el tiempo de carga de la aplicación. Con la adopción de componentes standalone como estándar en Angular 20, tenemos dos enfoques principales para implementar lazy loading: loadComponent() para componentes individuales y loadChildren() para cargar múltiples rutas.

Lazy loading de componentes standalone con loadComponent

La función loadComponent() es la forma más directa de cargar componentes standalone de manera lazy. Este enfoque es ideal cuando necesitas cargar un componente específico sin la complejidad de un módulo completo.

// app.routes.ts
import { Routes } from '@angular/router';

export const routes: Routes = [
  {
    path: 'dashboard',
    loadComponent: () => import('./features/dashboard/dashboard.component').then(m => m.DashboardComponent)
  },
  {
    path: 'profile',
    loadComponent: () => import('./features/profile/profile.component').then(m => m.ProfileComponent)
  },
  {
    path: 'settings',
    loadComponent: () => import('./features/settings/settings.component').then(m => m.SettingsComponent)
  }
];

El componente standalone que se carga debe estar correctamente configurado:

// features/dashboard/dashboard.component.ts
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule } from '@angular/router';

@Component({
  selector: 'app-dashboard',
  standalone: true,
  imports: [CommonModule, RouterModule],
  template: `
    <div class="dashboard">
      <h1>Dashboard</h1>
      <p>Contenido del dashboard cargado de forma lazy</p>
    </div>
  `
})
export class DashboardComponent { }

Lazy loading de rutas con loadChildren

Para casos más complejos donde necesitas cargar múltiples rutas relacionadas, loadChildren() sigue siendo la opción preferida. Sin embargo, la sintaxis se ha simplificado considerablemente desde Angular 15.

Enfoque moderno con default export:

// app.routes.ts
export const routes: Routes = [
  {
    path: 'admin',
    loadChildren: () => import('./features/admin/admin.routes').then(m => m.default)
  },
  {
    path: 'products',
    loadChildren: () => import('./features/products/products.routes').then(m => m.default)
  }
];

El archivo de rutas hijo utiliza un export por defecto:

// features/admin/admin.routes.ts
import { Routes } from '@angular/router';

export default [
  {
    path: '',
    loadComponent: () => import('./admin-layout/admin-layout.component').then(m => m.AdminLayoutComponent),
    children: [
      {
        path: 'users',
        loadComponent: () => import('./users/users.component').then(m => m.UsersComponent)
      },
      {
        path: 'reports',
        loadComponent: () => import('./reports/reports.component').then(m => m.ReportsComponent)
      },
      {
        path: '',
        redirectTo: 'users',
        pathMatch: 'full'
      }
    ]
  }
] as Routes;

Combinando loadComponent con rutas anidadas

Puedes combinar ambos enfoques para crear estructuras de rutas más flexibles:

// app.routes.ts
export const routes: Routes = [
  {
    path: 'shop',
    loadComponent: () => import('./features/shop/shop-layout.component').then(m => m.ShopLayoutComponent),
    children: [
      {
        path: 'catalog',
        loadComponent: () => import('./features/shop/catalog/catalog.component').then(m => m.CatalogComponent)
      },
      {
        path: 'cart',
        loadComponent: () => import('./features/shop/cart/cart.component').then(m => m.CartComponent)
      },
      {
        path: 'checkout',
        loadChildren: () => import('./features/shop/checkout/checkout.routes').then(m => m.default)
      }
    ]
  }
];

Integración con guards y resolvers

Los lazy-loaded routes funcionan perfectamente con guards y resolvers funcionales:

// features/admin/admin.routes.ts
import { inject } from '@angular/core';
import { Router } from '@angular/router';
import { AuthService } from '../../core/services/auth.service';

const adminGuard = () => {
  const authService = inject(AuthService);
  const router = inject(Router);
  
  if (authService.isAdmin()) {
    return true;
  }
  
  return router.parseUrl('/unauthorized');
};

export default [
  {
    path: '',
    loadComponent: () => import('./admin-layout/admin-layout.component').then(m => m.AdminLayoutComponent),
    canActivate: [adminGuard],
    children: [
      {
        path: 'users',
        loadComponent: () => import('./users/users.component').then(m => m.UsersComponent),
        resolve: {
          users: () => inject(UserService).loadUsers()
        }
      }
    ]
  }
] as Routes;

Cuándo usar cada enfoque

Usa loadComponent() cuando:

  • Necesitas cargar un componente individual
  • La funcionalidad es autocontenida
  • Quieres máxima simplicidad
  • El componente no tiene rutas hijas complejas

Usa loadChildren() cuando:

  • Necesitas cargar múltiples rutas relacionadas
  • Tienes una feature completa con varias páginas
  • Requieres rutas anidadas complejas
  • Quieres agrupar funcionalidad relacionada en un chunk separado

Esta aproximación moderna del lazy loading aprovecha la simplicidad de los componentes standalone mientras mantiene la flexibilidad necesaria para aplicaciones complejas. El resultado son bundles más pequeños y tiempos de carga iniciales más rápidos.

Estrategias de preloading y performance

Una vez configurado el lazy loading, el siguiente paso es optimizar cuándo y cómo se cargan estos módulos y componentes. Angular proporciona varias estrategias de preloading que permiten cargar código de forma inteligente en segundo plano, mejorando la experiencia del usuario sin impactar el tiempo de carga inicial.

Configuración de estrategias de preloading

Las estrategias de preloading se configuran en el router principal de la aplicación:

// app.config.ts
import { ApplicationConfig } from '@angular/core';
import { provideRouter, PreloadAllModules } from '@angular/router';
import { routes } from './app.routes';

export const appConfig: ApplicationConfig = {
  providers: [
    provideRouter(routes, {
      preloadingStrategy: PreloadAllModules
    })
  ]
};

PreloadAllModules: La estrategia más común

PreloadAllModules es la estrategia más utilizada que carga automáticamente todos los lazy routes en segundo plano una vez que la aplicación inicial está lista:

// Configuración básica
provideRouter(routes, {
  preloadingStrategy: PreloadAllModules
})

Esta estrategia es ideal para aplicaciones de tamaño medio donde quieres que todos los lazy routes estén disponibles rápidamente después de la carga inicial. Sin embargo, puede no ser óptima para aplicaciones muy grandes con muchas rutas que el usuario nunca visitará.

Preloading condicional personalizado

Para casos más avanzados, puedes crear estrategias de preloading personalizadas que carguen rutas basándose en condiciones específicas:

// custom-preloading.strategy.ts
import { Injectable } from '@angular/core';
import { PreloadingStrategy, Route } from '@angular/router';
import { Observable, of, timer } from 'rxjs';
import { switchMap } from 'rxjs/operators';

@Injectable({
  providedIn: 'root'
})
export class CustomPreloadingStrategy implements PreloadingStrategy {
  
  preload(route: Route, loadFn: () => Observable<any>): Observable<any> {
    // Solo precargar si la ruta tiene data.preload = true
    if (route.data?.['preload']) {
      console.log('Precargando ruta:', route.path);
      
      // Retrasar la precarga 2 segundos para no interferir con la carga inicial
      return timer(2000).pipe(
        switchMap(() => loadFn())
      );
    }
    
    return of(null);
  }
}

Configuración de la estrategia personalizada:

// app.config.ts
import { CustomPreloadingStrategy } from './custom-preloading.strategy';

export const appConfig: ApplicationConfig = {
  providers: [
    provideRouter(routes, {
      preloadingStrategy: CustomPreloadingStrategy
    })
  ]
};

Marcado de rutas para preloading condicional:

// app.routes.ts
export const routes: Routes = [
  {
    path: 'dashboard',
    loadComponent: () => import('./features/dashboard/dashboard.component').then(m => m.DashboardComponent),
    data: { preload: true } // Se precargará
  },
  {
    path: 'admin',
    loadChildren: () => import('./features/admin/admin.routes').then(m => m.default),
    data: { preload: false } // NO se precargará
  },
  {
    path: 'reports',
    loadComponent: () => import('./features/reports/reports.component').then(m => m.ReportsComponent)
    // Sin data.preload, no se precarga por defecto
  }
];

Preloading basado en condiciones del usuario

Puedes crear estrategias más inteligentes que consideren el rol del usuario o sus preferencias:

// role-based-preloading.strategy.ts
import { Injectable, inject } from '@angular/core';
import { PreloadingStrategy, Route } from '@angular/router';
import { Observable, of } from 'rxjs';
import { AuthService } from '../core/services/auth.service';

@Injectable({
  providedIn: 'root'
})
export class RoleBasedPreloadingStrategy implements PreloadingStrategy {
  private authService = inject(AuthService);
  
  preload(route: Route, loadFn: () => Observable<any>): Observable<any> {
    const userRole = this.authService.getUserRole();
    const requiredRole = route.data?.['requiredRole'];
    
    // Solo precargar si el usuario tiene el rol necesario
    if (!requiredRole || userRole === requiredRole || userRole === 'admin') {
      return loadFn();
    }
    
    return of(null);
  }
}

Análisis de bundles y optimización

Para medir el impacto de tus estrategias de lazy loading, Angular CLI proporciona herramientas de análisis:

Generar estadísticas de build:

ng build --stats-json

Analizar bundles con webpack-bundle-analyzer:

npm install -g webpack-bundle-analyzer
webpack-bundle-analyzer dist/your-app/stats.json

Esto te permite visualizar:

  • Tamaño de cada chunk lazy-loaded
  • Dependencias compartidas entre chunks
  • Oportunidades de optimización
  • Impacto de las estrategias de preloading

Mejores prácticas de performance

1. Combina preloading con network conditions:

// network-aware-preloading.strategy.ts
export class NetworkAwarePreloadingStrategy implements PreloadingStrategy {
  
  preload(route: Route, loadFn: () => Observable<any>): Observable<any> {
    // Verificar conexión de red antes de precargar
    if (navigator.connection && navigator.connection.effectiveType === '4g') {
      return loadFn();
    }
    
    // En conexiones lentas, solo precargar rutas críticas
    if (route.data?.['critical']) {
      return loadFn();
    }
    
    return of(null);
  }
}

2. Lazy loading granular por funcionalidad:

// Separar features grandes en chunks más pequeños
export const routes: Routes = [
  {
    path: 'ecommerce',
    children: [
      {
        path: 'products',
        loadComponent: () => import('./ecommerce/products/products.component').then(m => m.ProductsComponent)
      },
      {
        path: 'cart',
        loadComponent: () => import('./ecommerce/cart/cart.component').then(m => m.CartComponent)
      },
      {
        path: 'checkout',
        loadComponent: () => import('./ecommerce/checkout/checkout.component').then(m => m.CheckoutComponent)
      }
    ]
  }
];

3. Prefetch de datos críticos:

// Combinar preloading con resolvers para datos
{
  path: 'products',
  loadComponent: () => import('./products/products.component').then(m => m.ProductsComponent),
  resolve: {
    products: () => inject(ProductService).getProducts()
  },
  data: { preload: true }
}

Monitoreo de performance

Para medir el éxito de tus estrategias de preloading:

// performance.service.ts
import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class PerformanceService {
  
  measureRouteLoadTime(routePath: string) {
    const startTime = performance.now();
    
    return () => {
      const endTime = performance.now();
      console.log(`Ruta ${routePath} cargada en ${endTime - startTime}ms`);
    };
  }
  
  trackPreloadingEffectiveness() {
    // Medir cuántas rutas precargadas se visitan realmente
    const preloadedRoutes = new Set<string>();
    const visitedRoutes = new Set<string>();
    
    return {
      addPreloaded: (route: string) => preloadedRoutes.add(route),
      addVisited: (route: string) => visitedRoutes.add(route),
      getEffectiveness: () => {
        const intersection = new Set([...preloadedRoutes].filter(x => visitedRoutes.has(x)));
        return intersection.size / preloadedRoutes.size;
      }
    };
  }
}

Las estrategias de preloading bien implementadas pueden reducir significativamente los tiempos de navegación percibidos, especialmente cuando se combinan con guards, resolvers y una arquitectura de lazy loading bien planificada. La clave está en encontrar el equilibrio entre precarga inteligente y consumo de recursos, adaptándose a las necesidades específicas de tu aplicación y usuarios.

Alan Sastre - Autor del tutorial

Alan Sastre

Ingeniero de Software y formador, CEO en CertiDevs

Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, Angular es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.

Más tutoriales de Angular

Explora más contenido relacionado con Angular y continúa aprendiendo con nuestros tutoriales gratuitos.

Aprendizajes de esta lección

  • Comprender el concepto y beneficios del lazy loading en Angular.
  • Aprender a usar loadComponent para cargar componentes standalone de forma lazy.
  • Implementar loadChildren para lazy loading de rutas y módulos completos.
  • Configurar y personalizar estrategias de preloading para mejorar la performance.
  • Integrar guards, resolvers y análisis de bundles en rutas lazy-loaded.