Les Bonnes Pratiques Angular
Cela fait environs 5 ans que je me suis spécialisé en JavaScript avec le framework Angular. Lorsque j’étais encore à l’école, pour notre projet de fin d’étude, nous devions réaliser, entre autres, un site web. Nous avions fait le choix d’utiliser Angular pour pouvoir l’utiliser avec Ionic et réaliser en même temps une application hybride. Mais Notre manque de connaissance sur son architecture nous a causé pas mal de soucis.
Avec plus d’expérience, j’ai pu découvrir certaines bonnes pratiques que je vais vous partager !
Mais avant de les lister, petit point histoire sur la naissance d’Angular.
Histoire
Aujourd’hui, quand on parle d’Angular, on pense à la version CLI lancé en 2016 par l’équipe projet “Angular” de Google et avait pour but de remplacer AngularJS, créé en 2009.
On pourrait lister les différences entre ces 2 versions mais Wikipédia le fait mieux que moi, alors, je vais lister les principales :
- La notion de modularité : les fonctionnalités de base ont été déplacées vers des modules.
- L’utilisation du langage TypeScript, créé par Microsoft, utilisant le système de classe de la programmation orientée objet.
- Chargement dynamique des composants / modules
Ce framework compte 14 versions et l’équipe de Google lance 2 versions majeurs par an.
Maintenant que nous en savons un peu plus sur ce framework, je vais vous dire pourquoi il est important d’avoir de bonnes pratiques dessus, et plus généralement en programmation.
Pourquoi utiliser les bonnes pratiques
Vous avez pu le voir, avec votre expérience, que tout le monde code de façon différente.
Lorsque vous devez modifier le code de quelqu’un d’autre, il est relativement difficile de comprendre ce que cette personne a voulu faire. Il vous est même, peut-être, arrivé de ne pas comprendre votre propre code.
La lisibilité du code est une des raisons pour laquelle les bonnes pratiques sont importante. Mais nous pouvons en lister d’autres :
- Meilleure lisibilité du code : comme je l’ai expliqué plus haut, appliquer certaines règles permet de lire le code plus facilement. On peut lister des exemples de formatage : l’indication de de type, les indentations, l’utilisation d’une case (CamelCase, PascalCase, SnakeCase…), ajout de commentaire... Et en web, on peut utiliser des linter (ESLint, TSLint…). Cette pratique permet aussi une meilleure maintenabilité !
- Meilleure maintenabilité : un code lisible et commenté est plus facile à modifier.
- Meilleure gestion des performances : libérer la mémoire quand une variable n’est plus utilisée, optimiser les temps de chargement, utiliser du lazy-loading… Mieux gérer les ressources physiques permet de fluidifier l’expérience utilisateur.
Et je vous liste ici quelques exemples de bonnes pratiques pour Angular.
Liste des bonnes pratiques
Lisibilité
-
Prettier & TSLint
Ces deux éléments sont des formateur, un ensemble de règles, configurables, qui permet de repérer les éventuels problèmes de formatage. -
Convention de nommage
Les formateurs Prettier et TSLint possèdent des configurations sur le nommage des variables, fonctions, classes… Mais pas pour les fichiers. La commande Angular
permet de nommer correctement les fichiers. Cependant, on peut spécifier le nommage des fichiers comme ceci : [nom-du-fichier].[type].[extension] avec uniquement des minuscules.ng generate <schematic> [option]
Exemple :
app.component.ts // un composant
app.service.ts // un service
app.module.ts // un module
-
Commentaire
La majeure partie des développeurs, moi y compris, n’aimons pas faire documentation. Mais, bien commenter son code permet une meilleure compréhension du code.
Et il existe aujourd’hui des extensions permettant de générer automatiquement de la documentation en se basant sur des IA ! On peut citer Mintlify Doc Writer pour VSCode qui peut ajouter de la documentation pour plusieurs langages. -
Structure des fichiers
L’arborescence est un des éléments les plus importants pour avoir une base de code maintenable. On peut structurer son projet de diverse façon, en fonction surtout de l’équipe.
Ici je vais vous partager la structure que j’utilise le plus souvent :
- Avoir un module de base (core module) : c’est un module qui contient toutes les fonctionnalités de base. Pour savoir si un élément peut être dans ce module, il faut savoir combien de fois cet élément va être instancié dans notre application. On peut prendre par exemple un service d’authentification, un layout header / footer, les models…
- Avoir un module partagé (shared module) : ce module contient les éléments qui sont utilisés plusieurs fois. On peut donner en exemple des composants basiques (bouton custom, card…), des pipes (format date, sanitizer…)…
- Découpage de l’application en module fonctionnel : en plus d’avoir la possibilité d’améliorer les performances via le lazy-loading, découper l’application en module permet une meilleure visibilité. Prenons en exemple ce site, on a 3 zones fonctionnelles : la vitrine, le blog et la partie administration. Chacune de ses zones possèdes ses pages, ses composants, ses services…
-
La logique métier dans des services
Ceci permet de partager la logique métier dans plusieurs composants et de les alléger. -
Utilisation des alias pour les imports
Si, dans un composant, nous avons besoin d’importer un service se trouvant “à l’autre bout” de l’application, le chemin à écrire est assez long :
import { MyService } from '../../../../core/services/my.service';
Cependant, nous pouvons créer des alias via l’option “paths” dans le tsconfig.json :
{ "paths": { "@core/*": ["core/*"], } }
Et raccourcir le chemin à écrire :
import { MyService } from '@core/services/my.service';
-
Les index.ts
Ces fichiers permettent d’exporter plus facilement des éléments contenus dans un dossier.
Pour notre exemple du MyService, imaginons avoir plusieurs autres services utilisés dans un même composant. Nous serions forcés d’écrire :
// app.component.ts import { MyService } from "@core/services/my.service"; import { YourService } from "@core/services/your.service"; import { OurService } from "@core/services/our.service";
Mais on peut exporter ces services via un index.ts :
// core/service/index.ts export { MyService } from "./my.service"; export { YourService } from "./your.service"; export { OurService } from "./our.service";
Et ainsi alléger l’import dans le composant :
// app.component.ts import { MyService, YourService, OurService } from "@core/service";
On pourrait même créer un index.ts à la racine du dossier core et exporter tous nos services etautres éléments et faire dans le composantimport { MyService, YourService, OurComponent } from "@core";
Mais je vous conseille de garder un chemin assez clair pour vos imports puisque si, en gardant notre exemple, nous avons des models, composants, services exportés depuis l’index.ts à la racine du dossier core, on peut se retrouver avec un import “mélangé” :
// app.component.ts import { MyService, MyModel, YourService, OurComponent } from "@core";
-
Eléments génériques
Faire hériter des éléments d’un élément générique permet d’éviter la duplication de code (dans le cas où certains éléments se ressemble fortement). Ici on applique juste le plus fréquemment la logique d’héritage de langage orienté objet. -
Petits composants
Il faut avoir les plus petits composants possibles pour qu’ils soient le plus facilement maintenable et éviter les bugs. -
Utilisation des variables SCSS
Ici, on essaye d’avoir un fichier global pour définir le style des éléments, plus généralement, on va stocker dans des variables les couleurs.
Performances
-
Eviter les appels à fonction dans le template
Vous n’êtes pas sans savoir que le moteur de rendu d’Angular va s’exécuter régulièrement. Si on utilise des appels de fonction dans le template, ces fonctions vont être exécutées plusieurs fois. De plus, si le résultat ne change pas, il vaudrait mieux récupérer le retour de ces fonctions dans des variables et les initialiser dans le ngnOInit. -
ngFor avec TrackBy
Pour ce moteur de rendu, la modification d’une liste d’élément peut être assez long, surtout si la liste contient beaucoup d’éléments.
Pour lui faciliter la tâche, on peut utiliser une fonction trackBy qui va lui indiquer comment comparer les éléments de la liste :
trackByElements(index: number, element: Element): number { return element.id; }
<ng-container *ngFor="let element from liste; trackBy: trackByElements"> ... </ng-container>
-
Lazy-loading
Le lazy-loading permet de charger un élément quand l’utilisateur en a besoin. On peut en faire pour charger des images / vidéos…, mais aussi pour les modules et les composants !
Pour les modules, comme notre exemple avec ce site, on peut avoir dans le app-routing ceci pour lazy-loader le module blog :
//app-routing.module.ts const routes: Routes = [ { path: 'blog', loadChildren: () => import('./blog/blog.module').then(m => m.BlogModule) } ];
Vous pouvez aussi voir la documentation officielle sur ce sujet.
Pour des composants, je vous donne ce lien de la documentation officielle. -
Pour les Observables :
-
Ne pas faire de subscribe dans un subscribe
En plus d’un code difficilement maintenable, la fuite de mémoire est un gros risque.
On peut utiliser la fonctionforkJoin()
ou l’opérateurMergeMap()
:
forkJoin(this.myService.getAll(), this.yourService.getAll()).subsribe([myResult, yourResult] => ...); this.myService.getAll().pipe(mergeMap(myResult => this.yourService.getAll(myResult.id)));
-
Eviter les fuites de mémoire
Après le.subscribe()
, l’application va surveiller un changement sur l’observable… même si le composant où l’appel a été fait n’est plus utilisé.
Pour éviter de faire chuter les performances, on peut utiliser plusieurs méthodes comme l’opérateurtake()
qui permet de surveiller autant de fois un changement sur l’observable que le nombre donné en paramètre :
this.myService.pipe(take(1)).subscribe(...);
On peut aussi stocker lesSubscription
dans un tableau et les vider lors dungOnDestroy
:
export class MyComponent implements OnInit, OnDestroy { private subscriptions: Array<Subscription> = []; constructor(private myService: MyService) {} ngOnInit() { this.getElements(); } private getElements() { this.subscriptions.push(this.myService.getAll().subscribe(...)); } ngOnDestroy() { this.subscriptions.foreach(subscription => subscription.unsubribe()); } }
Ou encore utiliser l’opérateurtakeUntil()
qui permet de surveiller un changement jusqu’à ce que le paramètre émette une valeur :
export class MyComponent implements OnInit, OnDestroy { private destroy$ = new Subject<void>(); constructor(private myService: MyService) {} ngOnInit() { this.getElements(); } private getElements() { this.myService.getAll().pipe(takeUntil(this.destroy$)).subscribe(...); } ngOnDestroy() { this.destroy$.next(); this.destroy$.unsubscribe(); } }
-
Utiliser le pipe async
Le pipe async est votre meilleur ami avec les observables. Il permet de s’abonner à un observable dans le template et de se désabonner automatiquement !
<ng-container *ngIf="myObservable | async as datas"> ... </ng-container>
-
Ne pas faire de subscribe dans un subscribe
Autre
-
Eviter l’utilisation de ngModel
Son utilisation a été déprécié en version 6. Mais comment faire ? Les Reactive Forms ! Grâce à ça, vous bénéficié desformGroup
etformControl
permettant un plus grand contrôle sur vos formulaires en simplicité. -
Mise à jour
Comme je l’ai dit plus tôt, l’équipe Angular délivre 2 versions majeur par an, ce qui peut engendrer des changements bloquants. Mais l’équipe Angular nous donne 2 outils pour pouvoir faire ses mises à jour simplement :-
ng update
Cette commande met à jour toutes les dépendances d’Angular en surveillant leur compatibilité -
update.angular.io
Un site web où vous pouvez mettre la version actuelle de votre application et la version visée. Cet outil va lister toutes les étapes pour pouvoir faire la transition !
-
Pour finir
Vous avez pu le constater, l’utilisation des bonnes pratiques permet de maintenir plus facilement le code, éviter les bugs et améliorer les performances ! Certes, certaines ne sont pas facile à mettre en place mais n’oubliez pas que “Tous seul, on avance plus vite. Ensemble on avance plus loin”.
Si cet article vous a plu, n’hésitez pas à le partager et nous faire des retours ! Vos retours sont importants pour nous permettre de nous améliorer et vous proposer les meilleurs articles.
Vous pouvez d’ailleurs aller voir nos autres articles ou entamer une discussion pour une futur collaboration.