Angular est un framework puissant qui a été conçu pour pouvoir développer différents types d’applications web, de toutes tailles, avec un niveau de performance élevé. Et c’est souvent nous, développeurs, qui, par manque de connaissances ou par fainéantise (c’est pas bien hein !), finissons par mettre en place des pratiques qui dégradent drastiquement les performances de l’application.
Qui ne rêve pas d’une application qui serait à la fois rapide à charger, très réactive à l’utilisation et dont le livrable serait léger comme une plume ?
Je vais vous partager ici ma recette magique : une TO DO List d’actions (non exhaustives bien évidemment ?) qui vont permettre à vos applications de gagner en performances. Ces opérations influent principalement deux aspects : la limitation du temps de chargement et l’amélioration de la rapidité d’exécution de l’application.
Temps de chargement
L’astuce pour gagner en temps de chargement est d’avoir des bundles de petites tailles. Angular offre différents moyens pour y parvenir :
- Activer le build en mode Prod
Avec Angular CLI, il suffit de lancer la commande ng build avec l’option – – prod pour faire un build de l’application qui introduit beaucoup d’optimisations comme Minify, AOT, …
Lors de la compilation Ahead Of Time (AOT) les ressources de l’application sont précompilées durant la phase du build de l’application.
A l’inverse du JIT (Just In Time compilation), où c’est le navigateur qui s’occupe de la compilation, la compilation AOT garantie au navigateur de télécharger un code précompilé et prêt à l’exécution.
Quant à l’opération Minify, la taille du code est réduite en introduisant plusieurs transformations comme la suppression des espaces et des commentaires dans le code.
- Profiter du Tree Shaking
Tree Shaking est la technique qui permet d’éliminer le code mort lors de la création des bundles de l’application pendant la phase du build. Cette technique permet de garder seulement les parties du codes qui vont être exécutées, avec toujours pour bénéfice une taille de bundles moins importante.
Depuis la version 6 d’Angular, l’annotation @Injectable avec providedIn: ‘root’ permet de faire du treeshaking sur les dépendances. En privilégiant cette pratique aux déclarations dans les NgModules, on permet à Angular d’appliquer du Tree Shaking sur notre code.
Plusieurs libraires suivent le pas et implémentent aussi le Tree Shaking comme par exemple RxJS. Il est donc conseillé de mettre à jour cette dépendance (au moins vers la version 6) pour pouvoir en profiter.
- Mettre en place le Lazy Loading
C’est le mécanisme qui permet de charger les modules seulement lorsque c’est nécessaire au lieu de charger l’application entière à chaque ouverture. L’intérêt : afficher l’application beaucoup plus rapidement. Une fois mis en place, le lazy loading réduit fortement le temps initial de chargement de l’application.
- Penser à utiliser Angular Universal
Non non, ce ne sont pas les studios du framework… Angular Universal c’est l’ensemble des briques techniques qui permettent de générer côté serveur le rendu HTML d’une Single Page Application Angular.
Grâce à Universal, on améliore surtout le rendu de la première page d’une application et on optimise les performances sur les appareils mobiles et à faible consommation.
Rapidité d’exécution
- Profiter du Cache
Pour améliorer la réactivité de l’application, on peut limiter les appels serveurs coûteux en termes de mémoire et de temps de réponse, et utiliser des techniques de cache.
Le cache peut intervenir à plusieurs niveaux de l’interaction client-serveur et sous plusieurs formes. Pour ce qui est du cache géré explicitement côté client, une possibilité est de créer des services avec différentes stratégies de cache (Etag, durée de validité, etc…) qui s’interfacent avec d’autres services, interceptors ou resolvers.
Angular intègre aussi l’API service worker qui permet notamment de cacher les ressources de la page et de synchroniser de la donnée en arrière-plan. L’utilisation des services workers est incontournable lorsque vous avez des problématiques d’utilisation de l’application offline. Cela rentre bien souvent dans le cadre d’une Progressive Web App (PWA).
- Mettre à jour Angular et Angular CLI
Ok ce n’est pas facile de suivre le rythme (de nouvelles versions majeures d’angular quasiment tous les 5 mois actuellement, sans compter les versions mineures !), mais une chose est sûre : on a vraiment intérêt à suivre la tendance et à migrer aussi souvent notre application que possible pour profiter des avantages de l’optimisation des performances mais aussi de nouvelles features tout en renforçant l’aspect sécurité.
- Revoir ses ‘Third party packages’
Passer en revue les dépendances de l’application en se posant les questions suivantes pour chaque librairie : cette dépendance est-elle vraiment nécessaire ? Y-a-t-il un moyen plus simple pour réaliser la fonctionnalité souhaitée sans forcément ajouter cette dépendance ? Sinon, ma dépendance est-elle à jour ?
Lors de la revue et des mises à jour des librairies externes de mon application, je ne garde que les dépendances encore utilisées et je gagne en taille de build.
Ok c’est génial tout ça mais n’y a t-il pas de bonnes pratiques de coding qui vont faire gagner mon application Angular en performances ?
Il est toujours possible d’améliorer les Runtime Perf en faisant quelques ajustements de configuration ou en appliquant certaines pratiques :
- ChangeDetectionStrategy.OnPush
Par défaut à chaque événement asynchrone, Angular réalise un dirty checking pour essayer de détecter les changements sur toute l’arborescence d’un composant. Ce mécanisme peut s’avérer lourd et gourmand, notamment pour des applications de grande taille.
Pour désactiver ce mécanisme par défaut, on peut configurer la changeDetectionStrategy en OnPush. Attention tout de même si vous utilisez des objets non immuables car le changement sur ces objets ne sera pas détecté.
@Component({ selector: 'app-mycomponent', templateUrl: './mycomponent.html', styleUrls: './mycomponent.css', changeDetection: ChangeDetectionStrategy.onPush }) class MyComponent { }
- Détacher le change Detector
Pour désactiver la détection automatique des changements ‘programmatiquement’. on peut aussi désactiver le changeDetector d’un composant. Pour ce faire, il suffit d’accéder à la référence de ce changeDetector (injection d’une dépendance de type ChangeDetectorRef dans le composant). On applique un .detach() sur cette référence après l’initialisation de la vue.
L’appel au check des changements ne se fera qu’à la demande dans ce cas en appelant la méthode .detectChanges() de la même référence quand on en a besoin.
class AppComponent implements AfterViewInit { constructor (private changeDetectorRef: ChangeDetectorRef) {} ngAfterViewInit() { this.changeDetectorRef.detach(); } update() { // Run change detection only for this component when update() method is called this.changeDetectorRef.detectChanges(); } }
- Ne pas oublier de faire un unsubscribe sur les Observables
Ce ‘désabonnement’ doit être fait à la destruction du composant (au sein de la méthode ngOnDestroy).
- Passer par les views pour supprimer des éléments du DOM
Une autre pratique accentue les fuites de mémoire : c’est la suppression manuelle des éléments du DOM. Les éléments supprimés restent référencés en mémoire et la vue n’est pas synchronisée avec les éléments du DOM.
La solution consisterait à toujours passer par les Views pour supprimer ces éléments. On peut par exemple appeler la méthode clear() de l’instance de ViewContainerRef.
- Les console.log oubliés, ce n’est pas beau !
C’est vrai qu’on adore ajouter des console.log un peu partout dans notre code. Mais attention, il ne faut pas passer en prod avec ça ! Les console.log ont tendance à ralentir l’exécution de l’application, notamment lorsque la console du navigateur est ouverte.
Mot de la fin
Les performances d’une application relèvent toujours d’une problématique plus large que les points mentionnés ci-dessus. Il ne faut en effet pas oublier les pistes classiques par lesquelles on commence à investiguer en cas de problèmes comme l’infra des serveurs ou la configuration DNS/SSL qui sont parfois mal réalisées et génèrent des ralentissements.
Hichem Abdennadher