Optimización Extrema con Deferrable Views (@defer)
El Lazy Loading tradicionalmente se aplicaba a nivel de rutas. Si entrabas a una ruta, cargabas todo el módulo. ¿Pero qué pasa si tienes un componente muy pesado (como un gráfico complejo o un mapa) que está visible solo si el usuario hace scroll hacia abajo?
Aquí entra @defer. Esta sintaxis permite diferir la carga de partes específicas de tu plantilla de manera declarativa.
Sintaxis Básica y Bloques
El bloque @defer maneja varios estados automáticos:
@defer (on viewport) {
<heavy-chart-component />
} @loading (minimum 1s) {
<p>Cargando gráfico...</p>
} @placeholder {
<img src="chart-placeholder.png" alt="Placeholder" />
} @error {
<p>Hubo un error al cargar el componente.</p>
}
Desglose de los bloques:
- @defer: El contenido principal que se cargará perezosamente (el chunk de JS se separa automáticamente).
- @placeholder: Lo que se muestra antes de que comience la carga. Es instantáneo.
- @loading: Lo que se muestra mientras se descarga el código JS del componente.
- @error: Si falla la red o la carga del script.
Triggers (Disparadores)
La magia real está en cuándo se carga el contenido. Angular ofrece disparadores potentes:
on idle: (Por defecto) Se carga cuando el navegador está inactivo.on viewport: Se carga cuando el placeholder entra en la pantalla visible.on interaction: Se carga cuando el usuario hace clic o interactúa con el placeholder.on hover: Se carga al pasar el mouse por encima.on immediate: Se carga inmediatamente después de renderizar, pero en un chunk separado.when condition: Se carga cuando una expresión booleana sea verdadera.
Ejemplo Práctico: Dashboard con widgets pesados
Imagina un dashboard con un mapa, gráficos y una tabla de datos. Sin @defer, todo se carga al entrar:
<!-- Sin @defer: Todo se carga de golpe (bundle grande) -->
<app-header />
<app-kpi-cards />
<app-interactive-map /> <!-- 200KB de Leaflet -->
<app-chart-dashboard /> <!-- 150KB de Chart.js -->
<app-data-table /> <!-- 100KB de AG Grid -->
Con @defer, cada widget se carga solo cuando es necesario:
<app-header />
<app-kpi-cards />
@defer (on viewport) {
<app-interactive-map />
} @placeholder {
<div class="h-96 bg-gray-100 animate-pulse rounded-xl"></div>
}
@defer (on viewport) {
<app-chart-dashboard />
} @loading (minimum 500ms) {
<div class="flex items-center gap-2">
<spinner /> Cargando gráficos...
</div>
}
@defer (on interaction) {
<app-data-table />
} @placeholder {
<button class="btn">Haz clic para cargar la tabla completa</button>
}
Ejemplo: Comentarios de un Blog
No necesitamos cargar el componente de comentarios hasta que el usuario llegue al final del post.
@defer (on viewport) {
<app-comments-section [postId]="id" />
} @placeholder {
<div class="skeleton-comments">Cargar comentarios...</div>
}
Prefetching: Anticipar la carga
Puedes combinar @defer con prefetch para descargar el código antes de que se necesite:
@defer (on interaction; prefetch on hover) {
<app-heavy-editor />
} @placeholder {
<button>Abrir editor</button>
}
Cuando el usuario pasa el ratón por el botón, Angular empieza a descargar el JS en background. Cuando hace clic, el componente ya está listo.
Impacto en Core Web Vitals
Usar @defer correctamente mejora drásticamente:
- LCP (Largest Contentful Paint): Se reduce porque el contenido visible carga primero.
- TBT (Total Blocking Time): Menos JS para parsear al inicio.
- FID (First Input Delay): El hilo principal queda libre más rápido.
En aplicaciones con muchos componentes pesados, hemos visto reducciones del 30-50% en el bundle inicial.