20 recomendaciones para que tus aplicaciones de Angular funcionen más rápido | Parte 1…

20 recomendaciones para que tus aplicaciones de Angular funcionen más rápido | Parte 1: Optimización del renderizado

Celebremos el próximo lanzamiento de Angular 20 optimizando el rendimiento mediante el control del flujo, vistas diferidas, lazy-loading y estrategias de renderizado.

Con Angular 20 a la vuelta de la esquina, es un buen momento para repasar algunas de las funcionalidades y herramientas disponibles en Angular. En honor a este próximo lanzamiento, esta serie de artículos dividida en cuatro partes recopila 20 recomendaciones prácticas para ayudarte a aprovechar al máximo las mejoras de rendimiento integradas en el framework.

Alcanzar un mejor rendimiento en tiempo de ejecución no se trata solo de mantenernos al día con el ciclo oficial de versiones y ejecutar ng update @angular/core @angular/cli junto con otros pasos descritos en la guía oficial de actualización. Requiere también tomar decisiones estratégicas de arquitectura de software, ajustadas a las necesidades específicas de nuestros proyectos.

Este primer artículo se centra en optimizaciones de renderizado, que impactan directamente en el tiempo de carga, el uso de memoria y la capacidad de respuesta de la interfaz web en el navegador.

En las próximas semanas exploraremos:

Parte 2: ReactividadParte 3: Imágenes y estilosParte 4: Optimización en tiempo de compilación y monitoreo

Cada parte cubrirá un conjunto específico de recomendaciones para ayudarte a modernizar tus aplicaciones Angular de forma incremental, obteniendo mejoras de rendimiento medibles en cada etapa.

Recomendaciones para mejorar el renderizado

1. Completa la transición hacia la nueva sintaxis de control de flujo

Angular 20 establece una dirección definitiva hacia la nueva sintaxis de control de flujo introducida durante el período de renacimiento del framework. En noviembre de 2023, Angular 17 trajo — junto con otras funcionalidades clave — una alternativa sólida a las directivas estructurales tradicionales (*ngIf, *ngFor, *ngSwitch, etc.), orientándonos a adoptar una sintaxis más concisa para declarar condicionales y bucles mediante los bloques @if, @for y @switch.

Tres versiones más tarde, la obsolescencia de las directivas estructurales ya se hace oficial, con la intención de eliminarlas por completo en Angular 22.

El Angular Language Service v20 advierte cuando se importan las directivas estructurales tradicionales.

Más allá de una sintaxis mucho más clara, esta migración desbloquea una serie de beneficios tangibles en tiempo de ejecución:

La nueva sintaxis se compila completamente en tiempo de construcción, eliminando la sobrecarga que implica interpretar directivas estructurales en tiempo de ejecución.El algoritmo detrás de los bloques @for permite una mejor reconciliación y diferenciación en el DOM. A diferencia de la directiva *ngFor, que típicamente reconstruye nodos del DOM de forma innecesaria, @for utiliza un mecanismo de seguimiento por identidad para emparejar y reutilizar nodos de manera eficiente, especialmente cuando se emplea la palabra clave track. Esto resuelve problemas de larga data como fugas de memoria y actualizaciones excesivas del DOM durante la detección de cambios. El benchmark de js-framework-benchmark destaca estas mejoras (compara las columnas angular-cf-* con angular-ngfor-* para ver la diferencia).

En resumen, esta migración al nuevo control de flujo no se trata solo de evitar las advertencias de obsolescencia en la versión 20: ofrece una mejora sustancial de rendimiento.

Para ayudar a quienes desarrollamos en Angular con esta transición, el núcleo de Angular proporciona una esquemática de generación que reescribe automáticamente la sintaxis de las plantillas elegibles usando los nuevos bloques de control de flujo, automatizando gran parte del trabajo de migración. Sólo asegúrate de validar los cambios aplicados y de probar tu aplicación después de ejecutar el siguiente comando desde la CLI de Angular:

ng generate @angular/core:control-flow

2. Difiere los elementos que no sean críticos

A partir de los esfuerzos de modernización iniciados hace varios años con el compilador Ivy y la arquitectura standalone de componentes autónomos, Angular 17 pudo ofrecernos el concepto de vistas diferidas como un mecanismo declarativo de aplazar la carga y el renderizado de contenido que no sea imprescindible mostrar desde un inicio — incluyendo componentes de Angular anidados y otros elementos HTML — hasta que se cumplan condiciones específicas.

A continuación se muestra un ejemplo en pseudocódigo que ilustra cómo pueden usarse distintos disparadores de eventos y subloques de estado:

@defer (<<condición>>) {
<componente-diferido />
<section>Cualquier contenido HTML</section>
} @placeholder {
<div>Marcador de posición o esqueleto de contenido</div>
} @loading (after <<milisegundos>>; minimum <<milisegundos>>) {
<span>Indicador de carga…</span>
} @error {
<p>Acciones correctivas…</p>
}

Selecciona una condición de carga, marcador de posición, indicador de carga y estado de error adecuados según las decisiones técnicas que equilibren ganancias de rendimiento con una experiencia de usuario óptima. A continuación, se ofrece una breve explicación de cada condición:

@defer(when banderaBooleana) renderiza el bloque cuando una condición booleana se vuelve verdadera.@defer(on idle) se activa cuando el navegador ha completado todas las tareas críticas.@defer(on timer(x)) retrasa la carga x milisegundos.@defer(on viewport(#elemento)) se activa cuando el propio bloque, u otro elemento marcado con una variable de plantilla, entra en el área visible del navegador.@defer(on interaction(#elemento)) se activa cuando el usuario hace clic o enfoca el elemento referenciado.@defer(on hover(#elemento)) se activa cuando el usuario pasa el cursor sobre ese elemento.

Internamente, el compilador Ivy genera instrucciones de importación dinámica para cada componente, directiva o pipe utilizada dentro del bloque @defer. Luego, la vista correspondiente se renderiza una vez que todas sus dependencias han sido resueltas.

Cuando se configura correctamente, Angular compila ese contenido en un JavaScript separado que se carga únicamente cuando se activa. Algo ideal para secciones que aún no estén visibles en el navegador o componentes pesados que no deberían formar parte de la ruta de renderización crítica.

Para garantizar la correcta división de código en diferentes ficheros JavaScript, los componentes, directivas o pipes aplazados dentro de una vista diferida deben ser autónomos, o standalone. Además, no deben estar referenciados fuera del bloque @defer, ni ser accedidos mediante viewChild. De lo contrario, Angular los incluirá en el JavaScript principal y el navegador los cargará desde un inicio.

Ten en cuenta que al usar ng serve los componentes diferidos pueden seguir apareciendo empaquetados en el JavaScript principal, ya que el modo de desarrollo desactiva la división de código agresiva para priorizar tiempos de reconstrucción rápidos. Trata de utilizar un build de producción y revisa la actividad de red en Chrome DevTools (o herramientas similares) para confirmar que el componente diferido se cargue como un JavaScript separado en tiempo de ejecución.

Vale la pena mencionar que el contenido diferido también puede descargarse y almacenarse en caché de forma anticipada utilizando las declaraciones prefetch on idle o prefetch on timer(x) en combinación con los disparadores normales. Esto reduce la latencia en el momento en que la vista debe mostrarse.

Las vistas diferidas también pueden combinarse con las capacidades de hidratación incremental, que se encuentran estables desde Angular 20. En escenarios de renderizado del lado del servidor (SSR) o pre-renderizado, la hidratación puede ejecutarse mediante @defer utilizando los mismos disparadores de eventos. Este proceso activa el HTML prerenderizado al conectar los nodos generados con la lógica de ejecución de Angular, habilitando la interactividad en el cliente cuando se cumple la condición especificada.

@defer (hydrate on <<condición>>) {
<componente-diferido />
}

Puedes combinar el mecanismo de hidratación con disparadores de eventos para controlar cuándo se carga el contenido y cuándo se hidrata durante la carga inicial:

@defer (<<condición>>; hydrate on <<otra condición>>) {
<componente-diferido />
}

Si deseas que el bloque se hidrate de inmediato, utiliza hydrate on immediate. Por el contrario, usa hydrate on never para excluir explícitamente de la hidratación los bloques estáticos o aquellos que no sean interactivos.

3. Controla la carga diferida y la precarga de componentes

Mientras que @defer ofrece un control granular sobre cuándo se renderizan bloques específicos de la plantilla de un componente, la funcionalidad tradicional de lazy loading o carga diferida del Angular Router se enfoca en áreas funcionales separadas a través del sistema de enrutamiento de la aplicación. Ambas técnicas son complementarias y ayudan a reducir el trabajo inicial que el navegador debe realizar, actuando en diferentes capas de una aplicación de Angular.

Las rutas cargadas de manera diferida continúan siendo una de las estrategias más efectivas para dividir una aplicación grande en bloques más pequeños. Con la arquitectura standalone, puedes ejecutar la esquemática de generación de rutas con carga diferida para configurar automáticamente tantos componentes autónomos como sea posible, desacoplándolos del JavaScript principal:

ng generate @angular/core:route-lazy-loading

También es posible ajustar cuándo y cómo se precargan los componentes autónomos utilizando una estrategia de precarga personalizada o una de terceros, como la QuickLinkStrategy de Minko Gechev, o la OptInPreloadStrategy, OnDemandPreloadStrategy y NetworkAwarePreloadStrategy de John Papa. De esta manera puedes definir el comportamiento de precarga en función de factores como la visibilidad de enlaces, la interacción del usuario, las condiciones de red u otra lógica que se adapte al contexto de tu aplicación.

Para optimizar aún más el rendimiento, puedes experimentar complementando las capacidades nativas de Angular con las Speculation Rules API. Esta Web API experimental permite definir, mediante un archivo de configuración en formato JSON, qué navegaciones o recursos deben ser prefetch o prerender por el navegador web.

4. Considera aplicar estrategias híbridas de renderizado

En las dos recomendaciones previas nos enfocamos en optimizar cómo el navegador maneja tu aplicación después de que se ha cargado. Pero Angular también nos permite trasladar la responsabilidad del renderizado al servidor gracias a su sistema de renderizado híbrido, donde cada ruta puede configurarse para usar un modo de renderizado específico dentro de la configuración pasada a la función provideServerRouting(), lo que brinda un control mucho más preciso:

Client (CSR) permite que todo el renderizado ocurra en el navegador una vez que se haya cargado el JavaScript.Server (SSR) renderiza la página en el servidor por cada solicitud, entregando al cliente el HTML final para lograr un rasterizado más rápido y un mejor SEO.Prerender (SSG) genera el HTML completamente elaborado en tiempo de compilación, ofreciendo una entrega estática rápida sin necesidad de procesar en el lado del servidor cada solicitud.

Aquí tienes un ejemplo de cómo configurar estos modos de renderizado a nivel de ruta:

import { RenderMode, ServerRoute } from ‘@angular/ssr’;

export const serverRouteConfig: ServerRoute[] = [
{ path: ‘carrito’, renderMode: RenderMode.Client },
{ path: ‘catalogo’, renderMode: RenderMode.Server },
{ path: ‘producto/:id’, renderMode: RenderMode.Prerender },
];

Introducida en Angular 19, la capacidad de resolver parámetros de rutas durante el prerenderizado finalmente hizo posible generar versiones estáticas de rutas dinámicas con anticipación, lo cual es ideal para contenido que no cambia con frecuencia, como páginas de marketing o artículos de un blog. Por ejemplo, para prerenderizar páginas estáticas de productos disponibles a través de producto/:id, ahora podemos usar la función getPrerenderParams:

{
path: ‘producto/:id’,
renderMode: RenderMode.Prerender,
async getPrerenderParams() {
const catalogService = inject(CatalogService);
const productIds = await catalogService.getProductIds();
return productIds.map(productId => ({ productId }));
}
}

Una combinación bien planificada de estas técnicas ayuda a reducir la carga en el servidor, mejorar el SEO y, por lo general, resulta en un rendimiento percibido más rápido, especialmente cuando se complementa con las estrategias de hidratación mencionadas en la recomendación anterior.

5. Reduce el tamaño y la complejida del árbol de componentes

Incluso con una buena estrategia de carga diferida y un SSR/SSG correctamente implementados, el coste de renderizar subárboles del DOM derivados de componentes Angular gigantescos puede afectar significativamente el rendimiento en tiempo de ejecución. Reducir el tamaño y la profundidad de tus componentes es una forma eficaz de disminuir el uso de memoria y mejorar los tiempos de carga. Técnicas como la paginación de contenido y el renderizado condicional con bloques @if y @switch, como detallamos en la recomendación #1, nos ayudan a mantener en memoria solo los nodos DOM necesarios.

El Scrolling del Angular CDK es particularmente útil para listas muy largas, ya que renderiza únicamente los elementos visibles en el viewport. Combinémoslo con @defer para componentes anidados y así podremos reducir drásticamente la huella que dejamos en el DOM.

Para identificar rápidamente anidamientos innecesarios, Angular DevTools nos ofrece un inspector visual de la estructura del árbol de componentes. Optimizar la profundidad de nuestra jerarquía de componentes reducirá el esfuerzo computacional que Angular dedique a recorrer y verificar vistas profundamente anidadas en tiempo de ejecución.

Por último, asegúrate de dividir los componentes grandes en unidades más pequeñas con responsabilidades bien claras, y de mover lógica reutilizable o cualquier parte no relacionada directamente con los bindings de plantilla a servicios de Angular. Tener componentes ordenados simplifica el proceso de pruebas de una aplicación, y un árbol de componentes más pequeño también hace que la detección de cambios sea más eficiente, lo cual nos sirve como antesala perfecta para proceder con las siguientes optimizaciones.

Qué viene en la Parte 2: Reactividad

Cuando publiquemos el próximo artículo de esta serie, Angular 20.0.0 ya estará oficialmente disponible, brindándonos soporte estable para muchas de las características modernas introducidas en versiones recientes.

En la segunda entrega, cambiaremos el enfoque hacia el sistema de reactividad de Angular. Exploraremos el grafo de Signals para actualizaciones precisas y controladas, la detección de cambios sin Zone.js (Zoneless) para eliminar la sobrecarga innecesaria de patching, y cómo aplicar la API de Resource para simplificar la derivación de estado asincrónico.

Estas técnicas no solo nos ayudan a reducir cálculos innecesarios y el uso de memoria, sino que también mejoran significativamente nuestra experiencia de desarrollo.

No te pierdas la siguiente parte.

Read the English version of this article on the ng-conf blog:

20 Ways to Make Your Angular Apps Run Faster | Part 1: Rendering Optimizations

20 recomendaciones para que tus aplicaciones de Angular funcionen más rápido | Parte 1… was originally published in ngconf on Medium, where people are continuing the conversation by highlighting and responding to this story.

Leave a Comment

Your email address will not be published. Required fields are marked *