
Ce document contient une liste de pratiques qui nous aideront à augmenter les performances de nos applications angulaires. La "liste de contrôle des performances angulaires" couvre différents sujets - de la pré-rendu et du regroupement côté serveur de nos applications aux performances d'exécution et à l'optimisation de la détection des changements effectuée par le cadre.
Le document est divisé en deux sections principales:
Certaines pratiques ont un impact sur les deux catégories afin qu'il puisse y avoir une légère intersection, cependant, les différences dans les cas d'utilisation et les implications seront explicitement mentionnées.
La plupart des sous-sections répertorient les outils, liés à la pratique spécifique, qui peuvent nous rendre plus efficaces en automatisant notre flux de développement.
Notez que la plupart des pratiques sont valides pour HTTP / 1.1 et HTTP / 2. Les pratiques qui font une exception seront mentionnées en spécifiant à quelle version du protocole pourraient être appliquées.
enableProdModeChangeDetectionStrategy.OnPush*ngFor DirectivetrackByCertains des outils de cette section sont toujours en développement et sont susceptibles de changer. L'équipe Angular Core travaille à l'automatisation du processus de construction de nos applications autant que possible, donc beaucoup de choses se produiront de manière transparente.
Le regroupement est une pratique standard visant à réduire le nombre de demandes que le navigateur doit effectuer afin de livrer l'application demandée par l'utilisateur. Essentiellement, le Bundler reçoit en entrée une liste de points d'entrée et produit un ou plusieurs faisceaux. De cette façon, le navigateur peut obtenir l'ensemble de l'application en effectuant seulement quelques demandes, au lieu de demander séparément chaque ressource individuelle.
Au fur et à mesure que votre application se développe en train de tout en regrouper en un seul grand paquet serait à nouveau contre-productif. Explorez les techniques de division de code à l'aide de WebPack.
Les demandes HTTP supplémentaires ne seront pas une préoccupation avec HTTP / 2 en raison de la fonction de poussée du serveur.
Outillage
Les outils qui nous permettent de regrouper efficacement nos applications sont:
Ressources
Ces pratiques nous permettent de minimiser la consommation de bande passante en réduisant la charge utile de notre application.
Outillage
Ressources
Bien que nous ne voyions pas le caractère blanc de l'espace (un caractère correspondant au s regex), il est toujours représenté par des octets qui sont transférés sur le réseau. Si nous réduisons l'espace de nos modèles au minimum, nous serons respectivement en mesure de supprimer la taille du bundle du code AOT encore plus loin.
Heureusement, nous n'avons pas à faire cela manuellement. L'interface ComponentMetadata fournit les preserveWhitespaces de propriétés qui, par défaut, ont une valeur false signifiant qui par défaut, le compilateur angulaire coupera les espaces blancs pour réduire davantage la taille de notre application. Au cas où nous définissons la propriété sur true Angular préservera l'espace.
Pour la version finale de nos applications, nous n'utilisons généralement pas l'intégralité du code fourni par Angular et / ou une bibliothèque tierce, même celle que nous avons écrite. Grâce à la nature statique des modules ES2015, nous pouvons nous débarrasser du code qui n'est pas référencé dans nos applications.
Exemple
// foo.js
export foo = ( ) => 'foo' ;
export bar = ( ) => 'bar' ;
// app.js
import { foo } from './foo' ;
console . log ( foo ( ) ) ; Une fois que nous devons abri et bundle app.js , nous aurons:
let foo = ( ) => 'foo' ;
console . log ( foo ( ) ) ; Cela signifie que la bar d'exportation inutilisée ne sera pas incluse dans le pack final.
Outillage
Remarque: GCC ne prend pas encore en charge export * , ce qui est essentiel pour construire des applications angulaires en raison de l'utilisation intensive du modèle "baril".
Ressources
Depuis la sortie de la version 6 angulaire, l'équipe angulaire a fourni une nouvelle fonctionnalité pour permettre aux services d'être arboressibles, ce qui signifie que vos services ne seront pas inclus dans le pack final à moins qu'ils ne soient utilisés par d'autres services ou composants. Cela peut aider à réduire la taille du bundle en supprimant le code inutilisé du bundle.
Vous pouvez rendre vos services arborescentes en utilisant l'attribut providedIn pour définir où le service doit être initialisé lors de l'utilisation du décorateur @Injectable() . Ensuite, vous devez le supprimer de l'attribut des providers de votre déclaration NgModule ainsi que son énoncé d'importation comme suit.
Avant:
// 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 { }Aprè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 n'est injecté dans aucun composant / service, il ne sera pas inclus dans le bundle.
Ressources
Un défi pour les outils disponibles dans les outils sauvages (tels que GCC, Rollup, etc.) sont les modèles de type HTML des composants angulaires, qui ne peuvent pas être analysés avec leurs capacités. Cela rend leur soutien à la baisse des arbres moins efficace car ils ne savent pas quelles directives sont référencées dans les modèles. Le compilateur AOT transmet les modèles angulaires de type HTML à JavaScript ou TypeScript avec les importations de modules ES2015. De cette façon, nous sommes en mesure de secouer efficacement les arbres pendant le regroupement et de supprimer toutes les directives inutilisées définies par des bibliothèques angulaires, tierces ou par nous-mêmes.
Ressources
Compression de la pratique standard de la charge utile des réponses pour la réduction de l'utilisation de la bande passante. En spécifiant la valeur du Accept-Encoding d'en-tête, le navigateur laisse entendre le serveur quels algorithmes de compression sont disponibles sur la machine du client. D'un autre côté, le serveur définit la valeur de l'en-tête Content-Encoding de la réponse afin de dire au navigateur quel algorithme a été choisi pour comprimer la réponse.
Outillage
L'outillage ici n'est pas spécifique angulaire et dépend entièrement du serveur Web / application que nous utilisons. Les algorithmes de compression typiques sont:
Ressources
La pré-ficture des ressources est un excellent moyen d'améliorer l'expérience utilisateur. Nous pouvons soit pré-fouetter les actifs (images, styles, modules destinés à être chargés paresseusement, etc.) ou des données. Il existe différentes stratégies de pré-frappe, mais la plupart d'entre elles dépendent des spécificités de l'application.
Dans le cas où l'application cible a une énorme base de code avec des centaines de dépendances, les pratiques énumérées ci-dessus peuvent ne pas nous aider à réduire le bundle à une taille raisonnable (raisonnable pourrait être de 100 000 ou 2 m, ce qui dépend entièrement des objectifs commerciaux).
Dans de tels cas, une bonne solution pourrait être de charger certains des modules de l'application paresseusement. Par exemple, supposons que nous construisons un système de commerce électronique. Dans ce cas, nous pourrions vouloir charger le panneau d'administration indépendamment de l'interface utilisateur orientée utilisateur. Une fois que l'administrateur doit ajouter un nouveau produit, nous voudrions fournir l'interface utilisateur requise pour cela. Il peut s'agir que de la "page d'ajout de produit" ou de l'ensemble du panneau d'administration, en fonction de nos exigences d'utilisation / d'entreprise.
Outillage
Supposons que nous ayons la configuration de routage suivante:
// 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 première fois que l'utilisateur ouvre l'application à l'aide de l'URL: https://example.com/ il sera redirigé vers /dashboard qui déclenchera le routier paresseux avec dashboard du chemin. Afin de rendre le composant bootstrap du module, il devra télécharger le fichier dashboard.module et toutes ses dépendances. Plus tard, le fichier doit être analysé par la machine virtuelle JavaScript et évaluée.
Le déclenchement des demandes HTTP supplémentaires et la réalisation de calculs inutiles pendant la charge de page initiale est une mauvaise pratique car elle ralentit le rendu de page initial. Envisagez de déclarer l'itinéraire de page par défaut comme non-paresseux.
La mise en cache est une autre pratique courante ayant l'intention d'accélérer notre application en profitant de l'heuristique que si une ressource était récemment demandée, il pourrait être demandé à nouveau dans un avenir proche.
Pour les données de mise en cache, nous utilisons généralement un mécanisme de mise en cache personnalisé. Pour la mise en cache des actifs statiques, nous pouvons soit utiliser la mise en cache du navigateur standard ou les travailleurs de service avec l'API CacheStorage.
Pour accélérer les performances perçues de votre application, utilisez un shell d'application.
Le shell d'application est l'interface utilisateur minimale que nous montrons aux utilisateurs afin de les indiquer que l'application sera bientôt livrée. Pour générer un shell d'application de manière dynamique, vous pouvez utiliser Angular Universal avec des directives personnalisées qui affichent conditionnellement des éléments en fonction de la plate-forme de rendu utilisée (c'est-à-dire de tout, sauf le shell d'application lors de l'utilisation platform-server ).
Outillage
Ressources
Nous pouvons considérer le travailleur de service comme un proxy HTTP qui est situé dans le navigateur. Toutes les demandes envoyées par le client sont d'abord interceptées par le travailleur des services qui peuvent soit les gérer ou les transmettre dans le réseau.
Vous pouvez ajouter un travailleur de service à votre projet angulaire en exécutant ng add @angular/pwa
Outillage
Ressources
Cette section comprend des pratiques qui peuvent être appliquées afin de fournir une expérience utilisateur plus fluide avec 60 images par seconde (FPS).
enableProdModeEn mode développement, Angular effectue quelques vérifications supplémentaires afin de vérifier que la détection de modifications effectuée n'entraîne aucune modification supplémentaire à aucune des liaisons. De cette façon, les cadres assurent que le flux de données unidirectionnel a été suivi.
Afin de désactiver ces modifications de production, n'oubliez pas d'invoquer enableProdMode :
import { enableProdMode } from '@angular/core' ;
if ( ENV === 'production' ) {
enableProdMode ( ) ;
}L'AOT peut être utile non seulement pour obtenir un regroupement plus efficace en effectuant des tremblements d'arbres, mais aussi pour améliorer les performances d'exécution de nos applications. L'alternative d'AOT est une compilation à temps (JIT) qui est effectuée, donc nous pouvons réduire la quantité de calculs requis pour le rendu de notre application en effectuant la compilation dans le cadre de notre processus de construction.
Outillage
ng serve --prodRessources
Le problème habituel dans l'application (SPA) typique d'une seule page est que notre code est généralement exécuté dans un seul thread. Cela signifie que si nous voulons obtenir une expérience utilisateur fluide avec 60 images par seconde, nous avons au plus 16 ms pour l'exécution entre les cadres individuels en cours de rendu, sinon ils baisseront de moitié.
Dans des applications complexes avec un arbre de composants énormes, où la détection des changements doit effectuer des millions de chèques chaque seconde, il ne sera pas difficile de commencer à abandonner les cadres. Grâce à l'agnosticisme d'Angular et à la découplage de l'architecture DOM, il est possible d'exécuter l'ensemble de notre application (y compris la détection des changements) chez un travailleur Web et de laisser le fil d'interface utilisateur principal responsable uniquement du rendu.
Outillage
Ressources
Un gros problème du SPA traditionnel est qu'ils ne peuvent être rendus que lorsque le JavaScript entier requis pour leur rendu initial n'est disponible. Cela conduit à deux gros problèmes:
Le rendu côté serveur résout ce problème en pré-renvoyant la page demandée sur le serveur et en fournissant le balisage de la page rendue pendant la charge de page initiale.
Outillage
Ressources
Sur chaque événement asynchrone, Angular effectue une détection des changements sur l'ensemble de l'arborescence des composants. Bien que le code qui détecte des modifications soit optimisé pour la cache en ligne, cela peut encore être un calcul lourd dans des applications complexes. Un moyen d'améliorer les performances de la détection des changements est de ne pas l'exécuter pour les sous-arbres qui ne sont pas censés être modifiés en fonction des actions récentes.
ChangeDetectionStrategy.OnPush La stratégie de détection des changements OnPush nous permet de désactiver le mécanisme de détection des changements pour les sous-arbres de l'arbre des composants. En définissant la stratégie de détection des modifications dans n'importe quel composant en valeur ChangeDetectionStrategy.OnPush de valeur. Angular considérera les entrées comme différentes lorsqu'elle les compare aux entrées précédentes par référence, et le résultat de la vérification de référence est false . En combinaison avec des structures de données immuables, OnPush peut apporter de grandes implications de performance pour de tels composants "purs".
Ressources
Une autre façon de mettre en œuvre un mécanisme de détection des changements personnalisés est de detach et reattach le détecteur de changement (CD) pour un composant donné. Une fois que nous detach le CD Angular n'effectuera pas de vérification de l'ensemble du sous-arbre du composant.
Cette pratique est généralement utilisée lorsque les actions des utilisateurs ou les interactions avec les services externes déclenchent la détection des changements plus souvent que nécessaire. Dans de tels cas, nous voulons envisager de détacher le détecteur de changement et de le rattacher uniquement lorsque l'exécution de la détection des changements est nécessaire.
Le mécanisme de détection des changements d'Angular est déclenché grâce à la zone.js. Zone.js Patches de singe toutes les API asynchrones dans le navigateur et déclenche la détection des changements à la fin de l'exécution de tout rappel asynchrone. Dans de rares cas , nous pouvons vouloir que le code donné soit exécuté en dehors du contexte de la zone angulaire et donc, sans exécuter le mécanisme de détection des changements. Dans de tels cas, nous pouvons utiliser la méthode runOutsideAngular de l'instance NgZone .
Exemple
Dans l'extrait ci-dessous, vous pouvez voir un exemple pour un composant qui utilise cette pratique. Lorsque la méthode _incrementPoints est appelée, le composant commencera à incrémenter la propriété _points toutes les 10 ms (par défaut). L'incrémentation fera l'illusion d'une animation. Étant donné que dans ce cas, nous ne voulons pas déclencher le mécanisme de détection des changements pour l'ensemble de l'arborescence des composants, toutes les 10 ms, nous pouvons exécuter _incrementPoints en dehors du contexte de la zone de l'Angular et mettre à jour le Dom manuellement (voir le secteur de 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 ) ;
}
}AVERTISSEMENT : Utilisez cette pratique très attentivement uniquement lorsque vous êtes sûr de ce que vous faites, car s'il n'est pas utilisé correctement, cela peut conduire à un état incohérent du DOM. Notez également que le code ci-dessus ne s'exécutera pas dans les travailleurs Web. Afin de le rendre compatible avec les travailleurs Web, vous devez définir la valeur de l'étiquette en utilisant le rendu de l'Angular.
Angular utilise Zone.js pour intercepter les événements qui se sont produits dans l'application et exécute automatiquement une détection de modification. Par défaut, cela se produit lorsque la file d'attente de microtastes du navigateur est vide, ce qui, dans certains cas, peut appeler des cycles redondants. De V9, Angular fournit un moyen de fusionner les détections de changement d'événements en tournant ngZoneEventCoalescing , c'est-à-dire
platformBrowser ( )
. bootstrapModule ( AppModule , { ngZoneEventCoalescing : true } ) ; La configuration ci-dessus planifiera la détection des modifications avec requestAnimationFrame , au lieu de se brancher dans la file d'attente de microtasques, qui exécutera les vérifications moins fréquemment et consommera moins de cycles de calcul.
AVERTISSEMENT: NGZOneEventCoAscing: TRUE peut briser les applications existantes qui relaient sur l'exécution constante de la détection des changements.
Ressources
Comme argument, le décorateur @Pipe accepte un objet littéral avec le format suivant:
interface PipeMetadata {
name : string ;
pure : boolean ;
}Le drapeau pur indique que le tuyau ne dépend d'aucun état global et ne produit pas d'effets secondaires. Cela signifie que le tuyau renvoie la même sortie lorsqu'il est invoqué avec la même entrée. De cette façon, Angular peut mettre en cache les sorties pour tous les paramètres d'entrée avec lesquels le tuyau a été invoqué et les réutiliser afin de ne pas avoir à les recomputer à chaque évaluation.
La valeur par défaut de la propriété pure est true .
*ngFor Directive La directive *ngFor est utilisée pour rendre une collection.
trackBy Par défaut *ngFor identifie l'unicité de l'objet par référence.
Ce qui signifie que lorsqu'un développeur rompt la référence à l'objet lors de la mise à jour du contenu de l'élément, angulaire le traite comme la suppression de l'ancien objet et l'ajout du nouvel objet. Cela affecte la destruction de l'ancien nœud Dom dans la liste et l'ajout d'un nouveau nœud Dom à sa place.
Le développeur peut fournir un indice pour comment identifier l'unicité des objets: fonction de suivi personnalisé comme l'option trackBy de la directive *ngFor . La fonction de suivi prend deux arguments: index et item . Angular utilise la valeur renvoyée de la fonction de suivi pour suivre l'identité des éléments. Il est très courant d'utiliser l'ID de l'enregistrement particulier comme clé unique.
Exemple
@ 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 ;
}
} Le rendu des éléments DOM est généralement l'opération la plus coûteuse lors de l'ajout d'éléments à l'interface utilisateur. Le travail principal est généralement causé par l'insertion de l'élément dans le DOM et l'application des styles. Si *ngFor rend beaucoup d'éléments, les navigateurs (en particulier les plus âgés) peuvent ralentir et avoir besoin de plus de temps pour terminer le rendu de tous les éléments. Ce n'est pas spécifique à Angular.
Pour réduire le temps de rendu, essayez ce qui suit:
*ngFor de votre modèle. Habituellement, les éléments DOM inutiles / inutilisés découlent de l'extension du modèle encore et encore. Repenser sa structure rend probablement les choses beaucoup plus faciles.ng-container dans la mesure du possibleRessources
*ngForAngular exécute des expressions de modèle après chaque cycle de détection des changements. Les cycles de détection des changements sont déclenchés par de nombreuses activités asynchrones telles que les résolutions de promesse, les résultats HTTP, les événements de minuterie, les désespoir et les mouvements de souris.
Les expressions doivent se terminer rapidement ou l'expérience utilisateur peut faire glisser, en particulier sur des appareils plus lents. Pensez à la mise en cache des valeurs lorsque leur calcul est coûteux.
Ressources
La liste des pratiques évoluera dynamiquement au fil du temps avec des pratiques nouvelles / mises à jour. Dans le cas où vous remarquez quelque chose de manquant ou que vous pensez que l'une des pratiques peut être améliorée, n'hésitez pas à licencier un problème et / ou un RP. Pour plus d'informations, veuillez consulter la section "contributive" ci-dessous.
Si vous remarquez quelque chose de manquant, incomplet ou incorrect, une demande de traction sera grandement appréciée. Pour une discussion sur les pratiques qui ne sont pas incluses dans le document, veuillez ouvrir un problème.
Mit