Optimizar Aplicaciones en Angular Pt.1

¿Cómo mejorar el rendimiento de mi aplicación en angular?

Javier Ruiz Vázquez
Comunidad JavaScript
7 min readApr 25, 2019

--

Optimizing Angular Apps

Change Detection

Cada vez que ocurre un evento en la aplicación, Angular realiza una comprobación de detección de cambios para validar si hay algún cambio en un valor y requiere actualización de la vista.

Este proceso es muy rápido, en el flujo unidireccional de los datos, Angular verificará cada componente que puedan haberse visto afectado para revisar si hubo cambios, y a gran medida esta funcionalidad estará ligada al rendimiento de la aplicación.

Para evitar esto podemos decirle a Angular en que momento realice estas acciones, utilizando la estrategia OnPush.

OnPush cambiará la estrategia de detección de cambios de un componente, esto quiere decir que cuando se emita una detección de cambios esta omitirá el componente si su valor de entrada no ha sido cambiado. Por ende todos los componentes dentro de este también se omitirán, con esto se reduce el proceso de detección de cambios.

Ejemplo (Sin ChangeDetectionStrategy):

Ejemplo sin la estrategia de detección de cambios OnPush

En el ejemplo anterior cada vez que ocurra un evento en la aplicación se ejecutara el detector de cambios e imprimirá el console.log en este caso ocurrirá 4 veces, para utilizar la estrategia OnPush y ayudar al Angular a aminorar estas acciones necesitaremos agregar a nuestro componente la propiedad changeDetection darle el valor de ChangeDetectionStrategy.OnPush, esto le dirá a Angular que el componente solo depende del valor de entrada y solo debe revisar en los casos:

  1. El valor de entrada cambió
  2. Un evento ocurrió desde el componente o de los componentes hijos
  3. Manualmente ejecutando alguno de los eventos de la detección de cambio: (detectChanges(), markForCheck(), reattach())

Ejemplo (Aplicando ChangeDetectionStrategy):

Ejemplo con la estrategia de detección de cambios OnPush

Con esto al ver la consola en nuestro navegador podremos ver que solo se ejecuta 1 vez nuestro console.log que ocurre cuando se instancia el valor de tasks al componente.

Otra manera de hacer esto posible es como se comentaba arriba, manualmente:

Ejemplo con la estrategia de detección de cambios manual con ChangeDetectorRef

Realizando esto dentro del ciclo de vida del componente AfterViewInit nos aseguramos de que el primer render ocurra antes de desactivar la detección de cambios.

De igual manera podemos solicitar verificar cambios con: markForCheck(), para toda la aplicación, o bien solo para el componente con detectChanges(), o bien para reanudar el comportamiento normal con reattach().

Evita los Getters

En el ejemplo anterior obtenemos el valor de entrada de tasks mediante getter y setter, no hay mucho problema en usar estas funciones que nos permite javascript desde ES2015, son bastante útiles en ciertos casos, en otros no tanto ya que cada vez que se va a leer el valor de la variable se van a ejecutar estas funciones, es decir cada que haya un cambio ejecutará un setter para asignar el nuevo valor a la variable y un getter para obtener en la vista el valor reciente por que lo que sería el mismo caso de realizar cálculos complejos en el View.

La solución sería en base al ejemplo anterior:

Ejemplo remplazando evitando Getters

Más abajo veremos más a detalle como evitar el uso de cálculos complejos en la vista.

TrackBy

Manipular el DOM es una tarea costosa para la aplicación, y esto se vuelve evidente en un sencillo listado, usualmente utilizamos la directiva estructural *ngFor para realizar esta tarea.

Al Iterar un listado de elementos y mostrarlo en el DOM en una aplicación con Angular, lo que hace *ngFor es una validación de igualdad para comprobar si los elementos han cambiado, esto lo hace mediante una referencia y no por las propiedades del objeto.

Analicemos este ejemplo:

Ejemplo sin TrackBy

Una vez pasado 1s se agregará otro objeto al arreglo, lo que provocará que se ejecute una detección de cambios y angular pinte nuevamente los elementos en nuestro DOM, a pesar de que 2 de ellos ya existían en el DOM. Este problema se resuelve fácilmente implementando una función trackBy, con esto angular puede detectar que elemento se ha añadido o removido del listado de acuerdo a un identificador único, por lo que el cambió solo se hará a ese elemento.

Ejemplo (Utilizando TrackBy):

Ejemplo con TrackBy

Evitar cálculos complejos en la Vista

Realizar cálculos para transformar información en la vista es una mala opción ya que el problema viene por que Angular tiene que volver ejecutar la función cada que hay una detección de cambios, y si la función realiza una tarea grande puede ser costoso para el rendimiento de la aplicación.

Ejemplo (Realizando los cálculos en la vista):

Ejemplo realizando los cálculos en la vista

Lo ideal sería transformar la información antes de pasarla a la vista para evitar que cada vez que haya detección de cambios se ejecute nuevamente la función introducida.

Ejemplo (Evitando los cálculos en la vista):

Ejemplo sin realizar cálculos en la vista

Otra solución podría ser utilizar, pipes puros, que se ejecutan solamente cuando hay un cambio puro en el valor de entrada. Es decir no depende de ningún estado externo y no tiene efectos secundarios, esto permite a Angular almacenar en caché las salidas para todos los parámetros de entrada y permite reutilizar los valores en lugar de volver a calcularlos.

Ejemplo (utilizando Pipes):

Ejemplo sin realizar cálculos en la vista utilizando pipes

Para más detalle sobre Pipes (pure/impure) te dejo esta referencia en la documentación oficial de Angular.

Les dejo el proyecto en stackblitz…

Lazy Loading

Lazy loading es una de las mejores y más poderosas características de Angular, y una de las más utilizadas. Es un mecanismo donde en lugar de cargar la aplicación completa un inicio, se carga en partes según se van necesitando, lo cual reduce el tiempo de carga inicial en la aplicación, en pocas palabras no cargará algo que no se ocupe.

La mayoría de las aplicaciones contienen más de una página o bien módulos por lo que al si no se utilizara Lazy loading cargaría todos esos módulo o páginas desde un inicio, incluso si no te encuentras en la página.

Ejemplo (Sin utilizar Lazy loading):

Ejemplo sin utilizar Lazy Loading

Afortunadamente Angular nos permite una manera sencilla de cargar solo lo necesario, el único requisito para esto funcione es que cada página contenga su propio módulo y proporcione sus propias rutas. La ruta principal, en este caso la que contiene el componente WelcomeComponent, no debe ser cargada mediante Lazy loading, ya que es la página principal y al cargarla de esta manera generará un retraso.

Ejemplo (Aplicando Lazy Loading):

Ejemplo Lazy Loading Routing Module Padre
Ejemplo Lazy Loading Routing Module Hijo

También podemos prevenir la carga de estos módulos en base a condiciones, por ejemplo cuando tienes una aplicación basada en Roles normalmente cuentas con un menú ejemplo:

Admin — Perfil — Tareas

Un usuario normal puede ver su perfil y sus tareas pero no el de Admin por lo que no necesitaría cargar ese módulo, para esto podemos implementar un Guard en una propiedad del objeto en el arreglo de routes, llamado canLoad.

Esto nos permitirá no cargar nada en la aplicación a diferencia de canActivate, ya que suponiendo que sepas la ruta de Admin, al ingresarla en la URL del navegador la aplicación cargará el script necesario para el módulo, aunque el Guard no te permita ver la vista y te redireccione a otro lado. Esto lo puedes ver más a detalle en la consola (Chrome), en la pestaña de Sources.

Ejemplo (Lazy loading y canLoad):

Ejemplo aplicando Lazy Loading y canLoad

Cómo nota al utilizar canLoad no podrás pre-cargar tus módulos en caso de que estés usando esta estrategia de carga, es decir si en alguno de tus objetos de rutas tienes preload y canLoad no aplicará la funcionalidad de pre-carga.

Ejemplo (utilizando canLoad y preloadingStrategy):

Ejemplo aplicando Lazy Loading, canLoad y preloadingStrategy

Ahora solo cargará el código necesario y si es requerido para el funcionamiento o la vista, mejorando así el rendimiento de nuestra aplicación.

Remueve los espacios en blanco

Una optimización simple y rápida que se puede hacer es la de eliminar los espacios, esto ayuda a hacer un poco más pequeño el tamaño de la aplicación, a partir de la versión 5 de Angular se introdujo esta característica llamada preserveWhitespaces que te permite indicarle al compilador que elimine cualquier espacio en blanco entre los elementos, por default no esta habilitada esta opción, así que tenemos que hacerlo nosotros.

Para compilación JIT:

Aplicando preserveWhitespaces en JIT

Para compilación AOT:

Aplicando preserveWhitespaces en AOT

Dentro del decorador component específicamente:

Aplicando preserveWhitespaces en el Decorador Component

Si por alguna razón queremos conservar los espacios dentro de algún elemento, Angular provee una directiva para esta acción:

Aplicando preserveWhitespaces como directiva

Con esto al realizar la compilación de tu aplicación, el tamaño se reducirá un poco más.

Build Optimizer

Si estas utilizando el CLI, cuando realizas la compilación de tu aplicación para producción con el comando:

El flag prod activa las siguientes características de optimización:

  • Compilación AOT(Ahead of Time): Precompila los templates de los componentes.
  • Production mode: Despliega el modo de producción lo cual quita algunos procesos que se llevan en el modo desarrollo.
Aplicando enableProdMode para producción
  • Bundling: Concatenan tu archivos de en pequeños paquetes.
  • Minification: Remueve el exceso de espacios en blanco, comentarios y tokens opcionales.
  • Uglificaction: Reescribe el código para hacerlo más corto.
  • Dead code elimination: Remueve el código no referenciado y no usado.

El flag buildOptimizer se activa si se utiliza la opción aot, por default su valor es false.

Y para seguridad podemos utilizar el flag subresourceIntegrity ayuda a proteger tu aplicación de cualquier manipulación de recursos que pueda pasar especialmente si estas hospedando tus recurso o bien obteniéndolos desde un CDN donde no tienes control de los recursos que usas. Por default false.

Para más referencia puedes visitar la documentación oficial de Angular y la documentación oficial de Angular CLI.

Follow me on Medium or Twitter!, Si te gusto dale clap a la manita para saber que estoy haciendo las cosas bien, y déjame tus comentarios!!!

--

--