
Este documento contiene una lista de prácticas que nos ayudarán a aumentar el rendimiento de nuestras aplicaciones angulares. La "Lista de verificación de rendimiento angular" cubre diferentes temas, desde la pre-retención y la agrupación de nuestras aplicaciones hasta el rendimiento del tiempo de ejecución y la optimización de la detección de cambios realizada por el marco.
El documento se divide en dos secciones principales:
Algunas prácticas afectan ambas categorías, por lo que podría haber una ligera intersección, sin embargo, las diferencias en los casos de uso y las implicaciones se mencionarán explícitamente.
La mayoría de las herramientas de lista de subsecciones, relacionadas con la práctica específica, que pueden hacernos más eficientes al automatizar nuestro flujo de desarrollo.
Tenga en cuenta que la mayoría de las prácticas son válidas para HTTP/1.1 y HTTP/2. Las prácticas que hacen una excepción se mencionarán especificando a qué versión del protocolo se pueden aplicar.
enableProdModeChangeDetectionStrategy.OnPush*ngFor directivatrackByAlgunas de las herramientas en esta sección todavía están en desarrollo y están sujetas a cambios. El equipo de Angular Core está trabajando para automatizar el proceso de compilación para nuestras aplicaciones tanto como sea posible, por lo que muchas cosas suceden de manera transparente.
Bundling es una práctica estándar con el objetivo de reducir la cantidad de solicitudes que el navegador debe realizar para entregar la aplicación solicitada por el usuario. En esencia, el Bundler recibe como entrada una lista de puntos de entrada y produce uno o más paquetes. De esta manera, el navegador puede obtener la aplicación completa realizando solo unas pocas solicitudes, en lugar de solicitar cada recurso individual por separado.
A medida que su aplicación crece, agrupando todo en un solo paquete grande, nuevamente sería contraproducente. Explore las técnicas de división de código utilizando Webpack.
Las solicitudes HTTP adicionales no serán una preocupación con HTTP/2 debido a la función Push del servidor.
Estampación
Las herramientas que nos permiten agrupar nuestras aplicaciones de manera eficiente son:
Recursos
Estas prácticas nos permiten minimizar el consumo de ancho de banda reduciendo la carga útil de nuestra aplicación.
Estampación
Recursos
Aunque no vemos el carácter de espacio blanco (un personaje que coincide con el s regex) todavía está representado por bytes que se transfieren a través de la red. Si reducimos el espacio blanco de nuestras plantillas al mínimo, podremos soltar el tamaño del paquete del código AOT aún más.
Afortunadamente, no tenemos que hacer esto manualmente. La interfaz ComponentMetadata proporciona la Propiedad preserveWhitespaces que, por defecto, tiene un valor false , lo que significa que, de manera predeterminada, el compilador Angular recortará espacios en blanco para reducir aún más el tamaño de nuestra aplicación. En caso de que establezcamos la propiedad en true Angular preservará el espacio en blanco.
Para la versión final de nuestras aplicaciones, generalmente no utilizamos el código completo que proporciona Angular y/o cualquier biblioteca de terceros, incluso el que hemos escrito. Gracias a la naturaleza estática de los módulos ES2015, podemos deshacernos del código al que no se hace referencia en nuestras aplicaciones.
Ejemplo
// foo.js
export foo = ( ) => 'foo' ;
export bar = ( ) => 'bar' ;
// app.js
import { foo } from './foo' ;
console . log ( foo ( ) ) ; Una vez que saquemos y el app.js de árboles, obtendremos:
let foo = ( ) => 'foo' ;
console . log ( foo ( ) ) ; Esto significa que la bar de exportación no utilizada no se incluirá en el paquete final.
Estampación
NOTA: El CCC no admite export * todavía, lo cual es esencial para construir aplicaciones angulares debido al gran uso del patrón de "barril".
Recursos
Desde el lanzamiento de Angular Versión 6, el equipo Angular proporcionó una nueva característica para permitir que los servicios sean sacudibles, lo que significa que sus servicios no se incluirán en el paquete final a menos que sean utilizados por otros servicios o componentes. Esto puede ayudar a reducir el tamaño del paquete eliminando el código no utilizado del paquete.
Puede hacer que sus servicios se puedan deshacerse de sus servicios utilizando el atributo providedIn para definir dónde se debe inicializar el servicio al usar el decorador @Injectable() . Luego debe eliminarlo del atributo providers de su declaración NgModule , así como su declaración de importación de la siguiente manera.
Antes:
// app.module.ts
import { NgModule } from '@angular/core'
import { AppRoutingModule } from './app-routing.module'
import { AppComponent } from './app.component'
import { environment } from '../environments/environment'
import { MyService } from './app.service'
@ NgModule ( {
declarations : [
AppComponent
] ,
imports : [
...
] ,
providers : [ MyService ] ,
bootstrap : [ AppComponent ]
} )
export class AppModule { } // my-service.service.ts
import { Injectable } from '@angular/core'
@ Injectable ( )
export class MyService { }Después:
// app.module.ts
import { NgModule } from '@angular/core'
import { AppRoutingModule } from './app-routing.module'
import { AppComponent } from './app.component'
import { environment } from '../environments/environment'
@ NgModule ( {
declarations : [
AppComponent
] ,
imports : [
...
] ,
providers : [ ] ,
bootstrap : [ AppComponent ]
} )
export class AppModule { } // my-service.service.ts
import { Injectable } from '@angular/core'
@ Injectable ( {
providedIn : 'root'
} )
export class MyService { } Si MyService no se inyecta en ningún componente/servicio, entonces no se incluirá en el paquete.
Recursos
Un desafío para las herramientas disponibles en la silvestre (como GCC, Rollup, etc.) son las plantillas similares a HTML de los componentes angulares, que no se pueden analizar con sus capacidades. Esto hace que su soporte para sacudir árboles sea menos eficiente porque no están seguros de a qué directivas se hace referencia dentro de las plantillas. El compilador AOT transpila las plantillas angulares similares a HTML a JavaScript o TypeScript con importaciones del módulo ES2015. De esta manera, podemos sacudir eficientemente los árboles durante la agrupación y eliminar todas las directivas no utilizadas definidas por las bibliotecas angulares, de terceros o por nosotros mismos.
Recursos
Compresión de la práctica estándar de carga útil de las respuestas para la reducción del uso del ancho de banda. Al especificar el valor de la Accept-Encoding del encabezado, el navegador insinúa el servidor que algoritmos de compresión están disponibles en la máquina del cliente. Por otro lado, el servidor establece el valor para el encabezado Content-Encoding de la respuesta para decirle al navegador qué algoritmo se ha elegido para comprimir la respuesta.
Estampación
Las herramientas aquí no son específicas de Angular y dependen completamente de la web/servidor de aplicaciones que estamos utilizando. Los algoritmos de compresión típicos son:
Recursos
La recursos previos a la recursos es una excelente manera de mejorar la experiencia del usuario. Podemos preparar prefabistes (imágenes, estilos, módulos destinados a cargar perezosamente, etc.) o datos. Existen diferentes estrategias previas a la recolección, pero la mayoría de ellas dependen de los detalles de la aplicación.
En caso de que la aplicación objetivo tenga una gran base de código con cientos de dependencias, las prácticas enumeradas anteriormente pueden no ayudarnos a reducir el paquete a un tamaño razonable (razonable puede ser de 100k o 2m, nuevamente, depende completamente de los objetivos comerciales).
En tales casos, una buena solución podría ser cargar algunos de los módulos de la aplicación. Por ejemplo, supongamos que estamos construyendo un sistema de comercio electrónico. En este caso, es posible que deseemos cargar el panel de administración de forma independiente de la interfaz de usuario orientada al usuario. Una vez que el administrador tenga que agregar un nuevo producto, queremos proporcionar la interfaz de usuario necesaria para eso. Esto podría ser solo la "página de agregado" o todo el panel de administración, dependiendo de nuestro caso de uso/requisitos comerciales.
Estampación
Supongamos que tenemos la siguiente configuración de enrutamiento:
// Bad practice
const routes : Routes = [
{ path : '' , redirectTo : '/dashboard' , pathMatch : 'full' } ,
{ path : 'dashboard' , loadChildren : ( ) => import ( './dashboard.module' ) . then ( mod => mod . DashboardModule ) } ,
{ path : 'heroes' , loadChildren : ( ) => import ( './heroes.module' ) . then ( mod => mod . HeroesModule ) }
] ; La primera vez que el usuario abre la aplicación utilizando la URL: https://example.com/ se redirigirá a /dashboard que activará la ruta de lazo con dashboard de ruta. Para que Angular represente el componente Bootstrap del módulo, tendrá que descargar el dashboard.module de archivos. Module y todas sus dependencias. Más tarde, el archivo debe ser analizado por JavaScript VM y evaluado.
Activar solicitudes HTTP adicionales y realizar cálculos innecesarios durante la carga inicial de la página es una mala práctica, ya que ralentiza la representación inicial de la página. Considere declarar la ruta de página predeterminada como no perezoso.
El almacenamiento en caché es otra práctica común que tiene la intención de acelerar nuestra aplicación aprovechando la heurística de que si se solicitara recientemente un recurso, se podría solicitar nuevamente en el futuro cercano.
Para los datos de almacenamiento en caché, generalmente usamos un mecanismo de almacenamiento en caché personalizado. Para el almacenamiento en caché de los activos estáticos, podemos usar el almacenamiento en caché del navegador estándar o los trabajadores de servicios con la API de Cachestorage.
Para que el rendimiento percibido de su aplicación sea más rápido, use un shell de aplicación.
El shell de la aplicación es la interfaz de usuario mínima que mostramos a los usuarios para indicarles que la aplicación se entregará pronto. Para generar un shell de aplicación dinámicamente, puede usar Angular Universal con directivas personalizadas que muestran condicionalmente elementos dependiendo de la plataforma de renderizado usada (es decir, ocultar todo excepto el shell de la aplicación cuando se usa platform-server ).
Estampación
Recursos
Podemos pensar en el trabajador de servicios como un proxy HTTP que se encuentra en el navegador. Todas las solicitudes enviadas desde el cliente son interceptadas primero por el trabajador de servicio que puede manejarlas o pasarlas a través de la red.
Puede agregar un trabajador de servicio a su proyecto angular ejecutando ng add @angular/pwa
Estampación
Recursos
Esta sección incluye prácticas que se pueden aplicar para proporcionar una experiencia de usuario más suave con 60 cuadros por segundo (FPS).
enableProdModeEn el modo de desarrollo, Angular realiza algunas verificaciones adicionales para verificar que la detección de cambios de realización no resulte en ningún cambio adicional en ninguno de los enlaces. De esta manera, los marcos aseguran que se haya seguido el flujo de datos unidireccional.
Para deshabilitar estos cambios para la producción, no se olvide de invocar enableProdMode .
import { enableProdMode } from '@angular/core' ;
if ( ENV === 'production' ) {
enableProdMode ( ) ;
}AOT puede ser útil no solo para lograr una agrupación más eficiente realizando la exhibición de árboles, sino también para mejorar el rendimiento del tiempo de ejecución de nuestras aplicaciones. La alternativa de AOT es la compilación justo a tiempo (JIT) que se realiza en tiempo de ejecución, por lo tanto, podemos reducir la cantidad de cálculos requeridos para la representación de nuestra aplicación mediante la realización de la compilación como parte de nuestro proceso de compilación.
Estampación
ng serve --prodRecursos
El problema habitual en la aplicación típica de una sola página (SPA) es que nuestro código generalmente se ejecuta en un solo hilo. Esto significa que si queremos lograr una experiencia de usuario fluida con 60 fps, tenemos como máximo 16 ms para la ejecución entre los marcos individuales, de lo contrario, se lanzarán a la mitad.
En aplicaciones complejas con un gran árbol de componentes, donde la detección de cambios debe realizar millones de controles cada segundo, no será difícil comenzar a dejar caer los marcos. Gracias al agnosticismo de Angular y al estar desacoplado de DOM Architecture, es posible ejecutar toda nuestra aplicación (incluida la detección de cambios) en un trabajador web y dejar el hilo de la interfaz de usuario principal responsable solo de la representación.
Estampación
Recursos
Un gran problema del spa tradicional es que no se pueden renderizar hasta que todo el JavaScript requerido para su representación inicial esté disponible. Esto lleva a dos grandes problemas:
La representación del lado del servidor resuelve este problema mediante la representación previa de la página solicitada en el servidor y proporcionando el marcado de la página renderizada durante la carga de la página inicial.
Estampación
Recursos
En cada evento asincrónico, Angular realiza la detección de cambios en todo el árbol de componentes. Aunque el código que detecta los cambios está optimizado para el almacenamiento en línea, este todavía puede ser un cálculo pesado en aplicaciones complejas. Una forma de mejorar el rendimiento de la detección de cambios es no realizarlo para los subárboles que no se supone que se cambien en función de las acciones recientes.
ChangeDetectionStrategy.OnPush La estrategia de detección de cambios OnPush nos permite deshabilitar el mecanismo de detección de cambios para los subárboles del árbol de componentes. Estableciendo la estrategia de detección de cambios en cualquier componente al valor ChangeDetectionStrategy.OnPush . Angular considerará las entradas como diferentes cuando las compara con las entradas anteriores por referencia, y el resultado de la verificación de referencia es false . En combinación con estructuras de datos inmutables, OnPush puede traer grandes implicaciones de rendimiento para tales componentes "puros".
Recursos
Otra forma de implementar un mecanismo de detección de cambios personalizados es detach y reattach colocar el detector de cambios (CD) para un componente dado. Una vez que se detach el angular de CD no realizará la verificación de todo el subárbol componente.
Esta práctica se usa típicamente cuando las acciones del usuario o las interacciones con los servicios externos desencadenan la detección de cambios con más frecuencia de lo requerido. En tales casos, podemos considerar separar el detector de cambios y volver a colocarlo solo cuando se requiere realizar una detección de cambios.
El mecanismo de detección de cambios de Angular se está activando gracias a Zone.js. Zone.js Monkey Parche todas las API asincrónicas en el navegador y desencadena la detección de cambios al final de la ejecución de cualquier devolución de llamada de Async. En casos raros , podemos querer que el código dado se ejecute fuera del contexto de la zona angular y, por lo tanto, sin ejecutar el mecanismo de detección de cambios. En tales casos, podemos usar el método runOutsideAngular de la instancia NgZone .
Ejemplo
En el fragmento a continuación, puede ver un ejemplo para un componente que usa esta práctica. Cuando el método _incrementPoints se llama componente, comenzará a incrementar la propiedad _points cada 10 ms (por defecto). La incrementación hará la ilusión de una animación. Dado que en este caso, no queremos activar el mecanismo de detección de cambios para todo el árbol de componentes, cada 10 ms, podemos ejecutar _incrementPoints fuera del contexto de la zona de Angular y actualizar el DOM manualmente (ver el setter points ).
@ Component ( {
template : '<span #label></span>'
} )
class PointAnimationComponent {
@ Input ( ) duration = 1000 ;
@ Input ( ) stepDuration = 10 ;
@ ViewChild ( 'label' ) label : ElementRef ;
@ Input ( ) set points ( val : number ) {
this . _points = val ;
if ( this . label ) {
this . label . nativeElement . innerText = this . _pipe . transform ( this . points , '1.0-0' ) ;
}
}
get points ( ) {
return this . _points ;
}
private _incrementInterval : any ;
private _points : number = 0 ;
constructor ( private _ngZone : NgZone , private _pipe : DecimalPipe ) { }
ngOnChanges ( changes : any ) {
const change = changes . points ;
if ( ! change ) {
return ;
}
if ( typeof change . previousValue !== 'number' ) {
this . points = change . currentValue ;
} else {
this . points = change . previousValue ;
this . _ngZone . runOutsideAngular ( ( ) => {
this . _incrementPoints ( change . currentValue ) ;
} ) ;
}
}
private _incrementPoints ( newVal : number ) {
const diff = newVal - this . points ;
const step = this . stepDuration * ( diff / this . duration ) ;
const initialPoints = this . points ;
this . _incrementInterval = setInterval ( ( ) => {
let nextPoints = Math . ceil ( initialPoints + diff ) ;
if ( this . points >= nextPoints ) {
this . points = initialPoints + diff ;
clearInterval ( this . _incrementInterval ) ;
} else {
this . points += step ;
}
} , this . stepDuration ) ;
}
}Advertencia : use esta práctica con mucho cuidado solo cuando esté seguro de lo que está haciendo porque si no se usa correctamente puede conducir a un estado inconsistente del DOM. Además, tenga en cuenta que el código anterior no se ejecutará en los trabajadores web. Para que sea compatible con el trabajo web, debe establecer el valor de la etiqueta utilizando el renderizador de Angular.
Angular usa zona.js para interceptar eventos que ocurrieron en la aplicación y ejecuta una detección de cambios automáticamente. Por defecto, esto sucede cuando la cola MicroTask del navegador está vacía, lo que en algunos casos puede llamar ciclos redundantes. De V9, Angular proporciona una forma de fusionar las detecciones de cambio de eventos al encender ngZoneEventCoalescing , es decir
platformBrowser ( )
. bootstrapModule ( AppModule , { ngZoneEventCoalescing : true } ) ; La configuración anterior programará la detección de cambios con requestAnimationFrame , en lugar de conectarse a la cola de microtask, que ejecutará verificaciones con menos frecuencia y consumirá menos ciclos computacionales.
ADVERTENCIA: NgzoneEventCoalescing: Verdadero puede romper las aplicaciones existentes que transmiten constantemente ejecutar la detección de cambios.
Recursos
Como argumento, el decorador @Pipe acepta un objeto literal con el siguiente formato:
interface PipeMetadata {
name : string ;
pure : boolean ;
}La bandera pura indica que la tubería no depende de ningún estado global y no produce efectos secundarios. Esto significa que la tubería devolverá la misma salida cuando se invoca con la misma entrada. De esta manera, Angular puede almacenar en caché las salidas para todos los parámetros de entrada con los que se ha invocado la tubería, y reutilizarlas para no tener que recomputarlos en cada evaluación.
El valor predeterminado de la propiedad pure es true .
*ngFor directiva La directiva *ngFor se usa para representar una colección.
trackBy Por defecto *ngFor identifica la singularidad del objeto por referencia.
Lo que significa que cuando un desarrollador rompe la referencia al objeto durante la actualización del contenido Angular del elemento lo trata como eliminación del objeto anterior y la adición del nuevo objeto. Estos efectos en la destrucción del antiguo nodo DOM en la lista y agregar un nuevo nodo DOM en su lugar.
El desarrollador puede proporcionar una pista para Angular Cómo identificar la singularidad de los objetos: la función de seguimiento personalizado como la opción trackBy para la directiva *ngFor . La función de seguimiento toma dos argumentos: index y item . Angular usa el valor devuelto de la función de seguimiento para rastrear la identidad de los elementos. Es muy común usar la ID del registro particular como la clave única.
Ejemplo
@ Component ( {
selector : 'yt-feed' ,
template : `
<h1>Your video feed</h1>
<yt-player *ngFor="let video of feed; trackBy: trackById" [video]="video"></yt-player>
`
} )
export class YtFeedComponent {
feed = [
{
id : 3849 , // note "id" field, we refer to it in "trackById" function
title : "Angular in 60 minutes" ,
url : "http://youtube.com/ng2-in-60-min" ,
likes : "29345"
} ,
// ...
] ;
trackById ( index , item ) {
return item . id ;
}
} Renderizar los elementos DOM suele ser la operación más costosa al agregar elementos a la interfaz de usuario. El trabajo principal generalmente es causado por insertar el elemento en el DOM y aplicar los estilos. Si *ngFor representa muchos elementos, los navegadores (especialmente los más antiguos) pueden reducir la velocidad y necesitar más tiempo para terminar de representar todos los elementos. Esto no es específico de Angular.
Para reducir el tiempo de renderizado, intente lo siguiente:
*ngFor la sección de su plantilla. Por lo general, los elementos DOM innecesarios/no utilizados surgen de extender la plantilla una y otra vez. Repensar su estructura probablemente haga las cosas mucho más fáciles.ng-container cuando sea posibleRecursos
*ngForAngular ejecuta expresiones de plantilla después de cada ciclo de detección de cambios. Los ciclos de detección de cambios se desencadenan por muchas actividades asíncronas, como resoluciones prometedoras, resultados HTTP, eventos de temporizador, prensas clave y movimientos del mouse.
Las expresiones deben terminar rápidamente o la experiencia del usuario puede arrastrarse, especialmente en dispositivos más lentos. Considere los valores de almacenamiento en caché cuando su cálculo es costoso.
Recursos
La lista de prácticas evolucionará dinámicamente con el tiempo con prácticas nuevas/actualizadas. En caso de que note que falta algo o cree que cualquiera de las prácticas puede mejorarse, no dude en despedir un problema y/o un PR. Para obtener más información, eche un vistazo a la sección "contribuyente" a continuación.
En caso de que note algo que falta, incompleto o incorrecto, una solicitud de extracción será muy apreciada. Para la discusión de las prácticas que no están incluidas en el documento, abra un problema.
MIT