Comment améliorer les performances de votre application Angular

Publié: 2020-04-10

Quand on parle des meilleurs frameworks frontaux, il est impossible de ne pas mentionner Angular. Cependant, cela demande beaucoup d'efforts de la part des programmeurs pour l'apprendre et l'utiliser à bon escient. Malheureusement, il existe un risque que les développeurs qui ne sont pas expérimentés dans Angular puissent utiliser certaines de ses fonctionnalités de manière inefficace.

L'une des nombreuses choses sur lesquelles vous devez toujours travailler en tant que développeur frontend est la performance de l'application. Une grande partie de mes projets passés se concentrait sur de grandes applications d'entreprise qui continuent d'être étendues et développées. Les frameworks frontend seraient extrêmement utiles ici, mais il est important de les utiliser correctement et raisonnablement.

J'ai préparé une liste rapide des stratégies d'amélioration des performances les plus populaires et des conseils qui peuvent vous aider à augmenter instantanément les performances de votre application Angular . Veuillez garder à l'esprit que tous les conseils ici s'appliquent à Angular dans la version 8.

ChangeDetectionStrategy et ChangeDetectorRef

Change Detection (CD) est le mécanisme d'Angular pour détecter les changements de données et y réagir automatiquement. Nous pouvons répertorier le type de base des changements d'état d'application standard :

  • Événements
  • Requête HTTP
  • Minuteries

Ce sont des interactions asynchrones. La question est la suivante : comment Angular saurait-il que certaines interactions (telles que le clic, l'intervalle, la requête http) se sont produites et qu'il est nécessaire de mettre à jour l'état de l'application ?

La réponse est ngZone , qui est essentiellement un système complexe destiné à suivre les interactions asynchrones. Si toutes les opérations sont enregistrées par ngZone, Angular sait quand réagir à certains changements. Mais il ne sait pas exactement ce qui a changé et lance le mécanisme de détection des changements , qui vérifie tous les composants dans le premier ordre.

Chaque composant de l'application Angular possède son propre détecteur de changement, qui définit comment ce composant doit agir lorsque la détection de changement a été lancée - par exemple, s'il est nécessaire de restituer le DOM d'un composant (ce qui est plutôt une opération coûteuse). Lorsque Angular lance Change Detection, chaque composant est vérifié et sa vue (DOM) peut être restituée par défaut.

Nous pouvons éviter cela en utilisant ChangeDetectionStrategy.OnPush :

 @Composant({
  sélecteur : 'foobar',
  templateUrl : './foobar.component.html',
  styleUrls : ['./foobar.component.scss'],
  changeDetection : ChangeDetectionStrategy.OnPush
})

Comme vous pouvez le voir dans l'exemple de code ci-dessus, nous devons ajouter un paramètre supplémentaire au décorateur du composant. Mais comment cette nouvelle stratégie de détection des changements fonctionne-t-elle vraiment ?

La stratégie indique à Angular qu'un composant spécifique ne dépend que de ses @Inputs(). De plus, tous les composants @Inputs() agiront comme un objet immuable (par exemple, lorsque nous modifions uniquement la propriété dans @Input() d'un objet, sans modifier la référence, ce composant ne sera pas vérifié). Cela signifie que de nombreuses vérifications inutiles seront omises et cela devrait augmenter les performances de notre application.

Un composant avec ChangeDetectionStrategy.OnPush sera vérifié uniquement dans les cas suivants :

  • La référence @Input() va changer
  • Un événement sera déclenché dans le modèle du composant ou l'un de ses enfants
  • Observable dans le composant déclenchera un événement
  • Le CD sera exécuté manuellement à l'aide du service ChangeDetectorRef
  • le tube asynchrone est utilisé sur la vue (le tube asynchrone marque le composant à vérifier pour les modifications - lorsque le flux source émettra une nouvelle valeur, ce composant sera vérifié)

Si rien de ce qui précède ne se produit, l'utilisation de ChangeDetectionStrategy.OnPush dans un composant spécifique entraîne la non-vérification du composant et de tous les composants imbriqués après le lancement du CD.

Heureusement, nous pouvons toujours avoir le contrôle total de la réaction aux modifications de données en utilisant le service ChangeDetectorRef. Nous devons nous rappeler qu'avec ChangeDetectionStrategy.OnPush dans nos délais d'attente, demandes, rappels d'abonnements, nous devons lancer le CD manuellement si nous en avons vraiment besoin :

 compteur = 0 ;

constructeur (privé changeDetectorRef : ChangeDetectorRef) {}

ngOnInit() {
  setTimeout(() => {
    this.counter += 1000 ;
    this.changeDetectorRef.detectChanges();
  }, 1000);
}

Comme nous pouvons le voir ci-dessus, en appelant this.changeDetectorRef.detectChanges() dans notre fonction timeout , nous pouvons forcer le CD manuellement. Si le compteur est utilisé de quelque manière que ce soit dans le modèle, sa valeur sera actualisée.

Le dernier conseil de cette section concerne la désactivation permanente du CD pour des composants spécifiques. Si nous avons un composant statique et que nous sommes sûrs que son état ne doit pas être modifié, nous pouvons désactiver définitivement le CD :

 this.changeDetectorRef.detach()

Ce code doit être exécuté dans la méthode de cycle de vie ngAfterViewInit() ou ngAfterViewChecked(), pour être sûr que notre vue a été rendue correctement avant de désactiver l'actualisation des données. Ce composant ne sera plus vérifié pendant CD , sauf si nous déclenchons manuellement detectChanges().

Appels de fonction et getters dans le modèle

L'utilisation d'appels de fonction à l'intérieur de modèles exécute cette fonction à chaque fois que le détecteur de changement s'exécute. La même situation se produit avec les getters . Si possible, nous devrions essayer d'éviter cela. Dans la plupart des cas, nous n'avons pas besoin d'exécuter de fonctions dans le modèle du composant lors de chaque exécution de CD . Au lieu de cela, nous pouvons utiliser des tuyaux purs.

Tuyaux purs

Les tuyaux purs sont une sorte de tuyaux dont la sortie ne dépend que de son entrée, sans effets secondaires. Heureusement, tous les tuyaux dans Angular sont purs par défaut.

 @Tuyau({
    nom : 'majuscule',
    pur : vrai
})

Mais pourquoi devrions-nous éviter d'utiliser des pipes avec pure : false ? La réponse est à nouveau Détection de changement. Les canaux qui ne sont pas purs sont exécutés à chaque exécution de CD, ce qui n'est pas nécessaire dans la plupart des cas et diminue les performances de notre application. Voici l'exemple de la fonction que l'on peut changer en pure pipe :

 transform(valeur : chaîne, limite = 60, points de suspension = '...') {
  if (!value || value.length <= limite) {
    valeur de retour ;
  }
  const numberOfVisibleCharacters = value.substr(0, limit).lastIndexOf(' ');
  return `${value.substr(0, numberOfVisibleCharacters)}${ellipse}` ;
}

Et voyons la vue :

 <p class="description">tronquer(texte, 30)</p>

Le code ci-dessus représente la fonction pure - pas d'effets secondaires, la sortie ne dépend que des entrées. Dans ce cas, on peut simplement remplacer cette fonction par pure pipe :

 @Tuyau({
  nom : 'tronquer',
  pur : vrai
})
classe d'exportation TruncatePipe implémente PipeTransform {
  transform(valeur : chaîne, limite = 60, points de suspension = '...') {
    ...
  }
}

Et enfin, dans cette vue, nous obtenons le code, qui ne sera exécuté que lorsque le texte a été modifié, indépendamment de Change Detection .

 <p class="description">{{ texte | tronquer : 30 }}</p>

Modules de chargement et de préchargement paresseux

Lorsque votre application comporte plusieurs pages, vous devez absolument envisager de créer des modules pour chaque élément logique de votre projet, en particulier les modules de chargement différé . Considérons le simple code du routeur Angular :

 itinéraires const : itinéraires = [
  {
    chemin: '',
    composant : HomeComponent
  },
  {
    chemin: 'foo',
    loadChildren : ()=> import("./foo/foo.module").then(m => m.FooModule)
  },
  {
    chemin : 'barre',
    loadChildren : ()=> import("./bar/bar.module").then(m => m.BarModule)
  }
]
@NgModule({
  exporte : [RouterModule],
  importations : [RouterModule.forRoot(routes)]
})
classe AppRoutingModule {}

Dans l'exemple ci-dessus, nous pouvons voir que le fooModule avec tous ses actifs ne sera chargé que lorsque l'utilisateur essaie d'entrer une route spécifique (foo ou bar). Angular générera également un morceau séparé pour ce module . Le chargement différé réduira la charge initiale .

Nous pouvons faire quelques optimisations supplémentaires. Supposons que nous voulions que nos modules de chargement d'application fonctionnent en arrière-plan. Dans ce cas, nous pouvons utiliser le preloadingStrategy. Angular a par défaut deux types de preloadingStrategy :

  • Pas de préchargement
  • PréchargerTousLesModules

Dans le code ci-dessus, la stratégie NoPreloading est utilisée par défaut. L'application commence à charger un module spécifique à la demande de l'utilisateur (lorsque l'utilisateur souhaite voir un itinéraire spécifique). Nous pouvons changer cela en ajoutant une configuration supplémentaire au routeur.

 @NgModule({
  exporte : [RouterModule],
  importations : [RouterModule.forRoot(routes, {
       preloadingStrategy: PreloadAllModules
  }]
})
classe AppRoutingModule {}

Cette configuration provoque l'affichage de la route actuelle dès que possible et après cela, l'application essaiera de charger les autres modules en arrière-plan. Intelligent, n'est-ce pas ? Mais ce n'est pas tout. Si cette solution ne correspond pas à nos besoins, nous pouvons simplement écrire notre propre stratégie personnalisée .

Supposons que nous voulions précharger uniquement les modules sélectionnés, par exemple, BarModule. Nous l'indiquons en ajoutant un champ supplémentaire pour le champ de données.

 itinéraires const : itinéraires = [
  {
    chemin: '',
    composant : HomeComponent
    données : { précharger : faux }
  },
  {
    chemin: 'foo',
    loadChildren : ()=> import("./foo/foo.module").then(m => m.FooModule),
    données : { précharger : faux }
  },
  {
    chemin : 'barre',
    loadChildren : ()=> import("./bar/bar.module").then(m => m.BarModule),
    données : { préchargement : vrai }
  }
]

Ensuite, nous devons écrire notre fonction de préchargement personnalisée :

 @Injectable()
classe d'exportation CustomPreloadingStrategy implémente PreloadingStrategy {
  preload(route: Route, load: () => Observable<any>): Observable<any> {
    return route.data && route.data.preload ? load() : of(null);
  }
}

Et définissez-le comme preloadingStrategy :

 @NgModule({
  exporte : [RouterModule],
  importations : [RouterModule.forRoot(routes, {
       preloadingStrategy : CustomPreloadingStrategy
  }]
})
classe AppRoutingModule {}

Pour l'instant, seules les routes avec param { data: { preload: true } } seront préchargées. Le reste des routes agira comme si NoPreloading était défini.

La stratégie de préchargement personnalisée est @Injectable(), ce qui signifie que nous pouvons injecter certains services à l'intérieur si nous en avons besoin et personnaliser notre stratégie de préchargement de toute autre manière.
Avec les outils de développement d'un navigateur, nous pouvons étudier l'amélioration des performances par un temps de chargement initial égal avec et sans preloadingStrategy. Nous pouvons également consulter l'onglet réseau pour voir que des morceaux pour d'autres itinéraires se chargent en arrière-plan, tandis que l'utilisateur peut voir la page actuelle sans aucun retard.

fonction trackBy

Nous pouvons supposer que la plupart des applications angulaires utilisent *ngFor pour itérer sur les éléments répertoriés dans le modèle. Si la liste itérée est également modifiable, trackBy est absolument indispensable.

 <ul>
  <tr *ngFor="let product of products ; trackBy : trackByProductId">
    <td>{{ produit.titre }}</td>
  </tr>
</ul>

trackByProductId(index : nombre, produit : produit) {
  renvoie product.id ;
}

En utilisant la fonction trackBy, Angular est capable de suivre les éléments des collections qui ont changé (par identifiant donné) et de restituer uniquement ces éléments particuliers. Lorsque nous omettons trackBy, toute la liste sera rechargée, ce qui peut être une opération très gourmande en ressources sur DOM.

Compilation anticipée (AOT)

Concernant la documentation angulaire :

" (…) les composants et modèles fournis par Angular ne peuvent pas être compris directement par le navigateur, les applications Angular nécessitent un processus de compilation avant de pouvoir s'exécuter dans un navigateur "

Angular fournit les deux types de compilation :

  • Just-in-Time (JIT) - compile une application dans le navigateur au moment de l'exécution
  • Ahead-of-Time (AOT) - compile une application au moment de la construction

Pour une utilisation en développement, la compilation JIT doit couvrir les besoins des développeurs. Néanmoins, pour la construction de production, nous devons absolument utiliser l ' AOT . Nous devons nous assurer que l'indicateur aot dans le fichier angular.json est défini sur true. Les avantages les plus importants d'une telle solution incluent un rendu plus rapide, moins de requêtes asynchrones, une taille de téléchargement de framework plus petite et une sécurité accrue.

Sommaire

La performance de l'application est quelque chose que vous devez garder à l'esprit à la fois pendant le développement et la partie maintenance de votre projet. Cependant, la recherche de solutions possibles par vous-même peut prendre beaucoup de temps et d'efforts. Vérifier ces erreurs courantes et les garder à l'esprit pendant le processus de développement vous aidera non seulement à améliorer les performances de votre application Angular en un rien de temps, mais vous aidera également à éviter de futurs problèmes.

Libérer l'icône du produit

Faites confiance à Miquido pour votre projet Angular

Contactez-nous

Vous souhaitez développer une application avec Miquido ?

Vous songez à donner un coup de pouce à votre entreprise avec une application Angular ? Contactez-nous et choisissez nos services de développement d'applications angulaires.