Dans cet article, je m’adresse uniquement aux développeurs Angular qui veulent améliorer les performances de leurs applications.

L’objectif est que vous soyez capables de déterminer par vous-même si vous avez correctement optimiser votre projet Angular ou non.

Devez-vous apprendre le plus de technos possibles ?

Ou au contraire il vaut mieux vous spécialiser à fond sur une technologie ?

Et si vous choisissiez de vous spécialiser à fond sur une techno, comme savoir laquelle ?

C’est plutôt important que vous soyez capable de développer des applications performances :

Une application trop lente risque de vous pénaliser auprès de vos clients finaux, et de votre hiérarchie…

Et même si vous codez des projets personnels sur le côté, si votre application met plus de 3 secondes à charger… alors 70% de vos utilisateurs seront déjà partis !

Optimisation 1 : Le Lazy Loading(ou « chargement paresseux »)

En cherchant sur Internet, vous risquez de tomber sur des listes interminables de recommandations : “17 optimisations Angular à connaître”, “Améliorer la performance de votre projet Angular en 4 étapes”…

Mais il y a une recommandation que vous devez absolument mettre en place pour améliorer immédiatement la performance de votre application :

Il s’agit du Lazy Loading.

En effet, à chaque vous que vous ajoutez des composants, des services, des modules… à votre projet, votre application prend de plus en plus d’importance.

Au bout d’un moment, votre application va mécaniquement atteindre une taille critique et votre application prendra tellement de temps à charger initialement… que vos utilisateurs partir plutôt que d’utiliser votre application !

Heureusement, le mécanisme du Lazy Loading permet de ne charger que le strict minimum à vos utilisateurs, au fur et mesure de leur navigation sur votre projet :

Le fonctionnement du Lazy Loading est basé sur la navigation de l’utilisateur.

Au démarrage de votre application, vous ne chargez que le module permettant d’afficher votre page d’accueil. Ensuite, lorsque l’utilisateur souhaite accéder à la page de connexion, vous chargez seulement le module de connexion LoginModule.

Et c’est exactement le même fonctionnement si l’utilisateur souhaite accéder à la page d’inscription ou une autre page.

Le chargement paresseux présente donc de nombreux avantages :

  1. Le chargement initial de votre application est beaucoup plus rapide, car vous ne chargez qu’un seul module au lieu de charger l’ensemble de votre application. 
  2. Vous pouvez charger les fonctionnalités demandées par l’utilisateur. Vous accélérez donc le temps de chargement pour vos utilisateurs qui ne visitent que certaines parties de votre application Angular.
  3. Vous pouvez continuer à développer de nouvelles fonctionnalités sans alourdir votre projet, car les nouveaux modules sont chargés à la volée via le Lazy Loading.
  4. Enfin, le Lazy Loading vous oblige à mieux organiser votre code, car il est structuré autour de votre architecture en modules. Vous êtes donc obligés de mieux découper votre application, en regroupant chaque fonctionnalité dans un module.

Optimisation 2 : Le Preloading (pour évitez d’impatienter vos utilisateurs)

Une fois que le Lazy Loading est en place… et bien vous devez certainement vous poser la question suivante :

“Est-ce que la navigation de mes utilisateurs ne risquent pas d’être saccadé, car à chaque fois qu’ils vont sur une nouvelle page, il faut leur charger tout un module ?!” 

Eh bien non, pas d’inquiétudes de ce côté-là.

Votre application Angular va simplement être plus rapide, pour deux raisons.

D’abord, vos modules sont beaucoup plus petits en poids que votre application complète. Ils se chargeront donc beaucoup plus rapidement dans le navigateur de vos utilisateurs.

Ensuite, Angular propose une autre technique pour charger tous vos modules en arrière-plan, pendant que vos utilisateurs sont occupés sur une autre partie de votre application :

import { NgModule } from '@angular/core';
import { Routes, RouterModule, PreloadAllModules } from '@angular/router';
import { PageNotFoundComponent } from './core/components/page-not-found/page-not-found.component';

const routes: Routes = [
 { path: '', redirectTo: '/home', pathMatch: 'full' },
 { path: '**', component: PageNotFoundComponent }
];

@NgModule({
 imports: [RouterModule.forRoot(routes, {preloadingStrategy: PreloadAllModules })],
 exports: [RouterModule]
})
export class AppRoutingModule { }

Lorsque vos utilisateurs arriveront sur la page en question, Angular aura déjà chargé votre module en entier.

Et donc le chargement sera quasi-instantanée pour vos utilisateurs !

Optimisation 3 : Loadtime vs Runtime…

Ensuite, à partir de là, vous avez le plus important côté optimisation.

Maintenant, il reste toujours beaucoup d’optimisations possibles, ou de problèmes à rectifier, et il n’est pas toujours facile de s’organiser de savoir ce que vous devez optimiser en priorité.

Pour s’y retrouver, ce que je fais est de regrouper les optimisations restantes en 2 catégories : 

  • Les optimisations de loadtime : L’objectif est d’optimiser le temps de chargement la première fois que votre projet se chargera dans le navigateur du client.
  • Les optimisations de runtime : L’objectif est d’améliorer les performances de votre application pendant l’utilisation de votre projet, au fur et à mesure que l’utilisateur consomme votre application.

Donc à partir de maintenant, demandez-vous de ce que vous voulez optimiser en premier, afin d’améliorer les performances de votre projet Angular.

Nous allons regarder ensemble comment améliorer chacun de ces point restants.

Optimisation 4 : Utiliser la compilation Ahead-of-Time

Nous devons maintenant terminer nos optimisations de loadtime.

Là-dessus, vous avez fait une grosse partie du boulot, grâce aux mécanismes de Lazy Loading et de Preloading.

Mais il reste un élément très important : utiliser la compilation Ahead-of-Time (AoT) plutôt que la compilation Just-in-Time (JiT).

La compilation AOT et JIT ? Que signifie ce charabia ?

Alors, il n’y a pas de raison de faire compliquer. Commençons par les présentations :

  • JiT signifie Just-In-Time. C’est ce qu’on utilise durant la phase de développement, car Angular va compiler nos composants en fonction JavaScript, pendant l’exécution de l’application, grâce à un compilateur interne à Angular.
  • AoT signifie Ahead-of-Time. L’idée ici est de compiler nos composants 1 seul fois pendant le processus de build. En effet, La compilation des templates de nos composants est invariante, c’est-à-dire qu’un même template engendrera toujours le même code en sortie (de la même manière que la compilation TypeScript par exemple). On peut donc compiler les templates de nos composants pendant le processus de build, plutôt qu’à la volée pendant le fonctionnement de votre application.

Mais, quelle compilation dois-je privilégier pour les utilisateurs finaux ? 🤔

Alors, sans hésiter la compilation Ahead-of-Time, car ses avantages sont clairs :

  • On économise en poids sur le livrable, et on gagne en performance, car la phase de compilation des templates n’a pas à être effectuée, puisque tout a été pris en charge lors du build.
  • L’autre avantage important est que l’on va être capable de détecter les erreurs de syntaxe dans les templates plus rapidement (appel à une méthode du composant inexistante, utilisation d’une variable non déclarée, etc.). En effet, comme la compilation à lieu lors de la phase de build plutôt qu’à l’exécution de l’application, on pourra corriger nos erreurs plus tôt.
  • Cependant, il faut bien qu’il y est un inconvénient. Le temps de build de la compilation avec AoT est plus long qu’avec JiT (mais ce n’est pas très grave, car on prendra simplement quelques instants en plus pour préparer notre livrable lors du déploiement).

Afin d’être plus concis, retenez que :

  • Pendant la phase de développement : Vous devez utiliser la compilation Just-In-Time, qui est configuré par défaut lorsque vous utilisez la commande ng serve.
  • Pendant la phase de production : Vous devez créer votre livrable avec la compilation Ahead-of-Time. La commande permettant de faire cela est ng build –prod.

C’est donc la compilation Aot que l’on utilisera pour créer notre livrable.

Nous allons donc tout de suite exécutez la commande suivante à la racine de votre application :

ng build --prod

L’exécution de cette commande devrait prendre un peu de temps, mais nous utilisons bien la compilation AoT. 👍

La compilation AoT est plus stricte au niveau des erreurs remontées. Pas de panique donc, si vous voyez des lignes rouges apparaître dans votre terminal de commande. Les erreurs sont très bien indiquées, et vous n’aurez qu’à les corriger les unes après les autres, jusqu’à ce que la compilation fonctionne.

Optimisation 5 : Les dernières optimisations de runtime (RxJS & Interactions avec le DOM)

On va attaquer le dernier maillon de l’optimisation d’une application Angular. Il s’agit des optimisations de runtime.

Là, on va distinguer les optimisations possibles en 2 catégories :

  • Améliorer la performance de vos appels réseaux et éviter les fuites de mémoires dans votre application Angular. Cela revient principalement à utiliser correctement la programmation réactive RxJS.
  • Minimiser les interactions entre vos templates et vos composants, car les interactions avec le DOM sont toujours des opérations les plus coûteuses en ressources dans une SPA (Single Page Application).

Pour commencer, côté RxJS, je vous recommande de systématiquement vous désabonner de vos Observables lorsque vous mettez en place un abonnement :

ngOnInit() {
   this.todoService.getTodoList().subscribe(todoList => this.todoList = todoList)
}

Dans ce cas, si vous ne vous désabonnez pas de ce flux via la méthode unsubscribe, et bien à chaque fois que votre composant va être instancié, la méthode ngOnInit va remettre en place un abonnement…

Petit à petit, votre application va devenir de plus en plus lente, jusqu’à ramer complètement !

Mais comme la gestion des abonnements et des désabonnements des flux peut être assez complexe (car on touche à la programmation réactive), je vous propose plutôt de simplement se baser sur le pipe async.

C’est vraiment un cadeau que nous ont fait les équipes d’Angular, il suffit d’utiliser ce pipe async, et tous nos problèmes seront réglés !

En effet, Angular s’occupera automatiquement de nos abonnements, désabonnements et extraction des données du flux :

ngOnInit() {
   this.todoList$ = this.todoService.getTodoList();
}

Puis se sert du pipe async pour utiliser un Observable directement dans le template :

<div *ngFor="let todo of todoList$ | async">
  {{ todo.title }}
</div>

Vous obtenez ainsi un code de meilleur qualité, performant côté programmation réactive,

Le tout avec moins de code que précédemment !

Côté optimisation dans l’interaction avec vos templates, là vous trouverez énormément de choses sur Intenet :

  • Utiliser trackBy avec la directive *ngFor…
  • Définir une stratégie de détection de changement OnPush lorsque cela est nécessaire…
  • Toujours éviter d’utiliser des getters et des fonctions dans vos templates…
  • Essayer d’utiliser des pipes purs lorsque cela est possible…
  • Etc….

Retenez que toutes ses optimisations ont un seul et même objectif : minimiser les interactions avec le DOM, car ce sont toujours des opérations coûteuses dans un projet web.

Ensuite, à vous devoir si vous disposez du temps nécessaires pour implémenter tout cela, pour continuer à économiser quelques millisecondes par-ci par-là.

En tout cas, le plus important est que vous soyez maintenant capable de déterminer par vous-même une stratégie d’optimisation claire pour votre projet Angular, sans passer par une liste interminable de recommandations complétement désordonnées.

Votre plan d’action (étape par étape)

  1. Mettre en place le Lazy Loading dans votre projet Angular.
  2. Définir une stratégie de Preloading pour le chargement de vos modules.
  3. Utiliser la compilation Ahead-of-Time lors de vos déploiements.
  4. Se servir du pipe Async pour éviter les fuites de mémoires lors de la manipulation de vos flux de données.
  5. Minimiser les interactions avec le DOM grâce à diverses optimisations : trackBy, OnPush, éviter les getters dans vos templates, pipes purs, etc…

Vous devriez maintenant être plus à l’aise par rapport à votre stratégie d’optimisation, et vous sentir en confiance par rapport à la future performance de votre application.

Recevez 1H de formation OFFERTE pour Réussir votre prochain Projet Angular en Entreprise