Episode 3 : Le socle mis en place par Angular CLI est-il suffisant pour se lancer dans des développements ? Ne manque t’il pas un architecture modulaire minimal avant de se lancer dans l’ajout des premiers composants ?

Vous avez généré un socle pour votre future application avec Angular CLI. Cependant, les outils ne peuvent pas faire tout le travail à notre place. A partir de maintenant, c’est à nous de prendre le relais, afin de poser les bases de l’architecture technique du projet. Et vous verrez que même si Angular CLI est là pour nous aider, nous n’allons pas nous ennuyer ! 

Par où commencer ?

 Angular CLI a déjà fait beaucoup de travail pour nous, et pourtant nous avons déjà un grand nombre de questions : 

  • Comment découper mon application en modules ? Combien de modules dois-je créer ? 
  • Où placer mes composants, comme la barre de navigation par exemple ? 
  • Dans quel dossier je dois regrouper mes services ?  
  • Comment modéliser mes entités avec des classes TypeScript ? 
  • Ou placer les fichiers nécessaires pour l’authentification ? 

En effet, si votre application est de taille suffisamment importante, elle devrait être composée de deux parties distinctes :

  • Une partie publique accessible par tous les visiteurs, authentifiés ou non. On retrouve dans cette partie la page d’accueil, la page “A propos”, le formulaire d’inscription et de connexion, etc. 
  • Une partie privée correspond à notre application en tant que telle : la gestion des tâches, l’accès au tableau de bord, etc. Cette partie n’est accessible que pour les utilisateurs qui disposent d’un compte sur l’application et qui se sont authentifiés.

Notre allons mettre en place une architecture technique qui fait la distinction entre ces deux parties. De plus, l’architecture devra respecter les bonnes pratiques de développement recommandées par Google, et être suffisamment évolutive pour faire face aux développements de nouvelles fonctionnalités. 

Les différents types de modules 

Les modules sont les éléments les plus macroscopiques d’une application Angular. Ils servent à centraliser tous les autres éléments d’une application : les composants, les services, les pipes, etc. Je vous recommande fortement de définir à l’avance quels modules vous aurez besoin pour votre projet.

Il existe plusieurs types de module dans une application Angular. Vous devez déjà en connaître certains :  

  • Le module racine : C’est le module parent de tous les autres modules dans une application. Il est nommé AppModule par convention, et Angular CLI l’a déjà généré dans le fichier app.module.ts
  • Les modules natifs d’Angular : On retrouve le RouterModule pour la gestion des routes, le HttpClientModule pour la gestion des requêtes réseaux, ou encore le FormsModule pour la gestion des formulaires. 
  • Les modules de définition des routes : Il s’agit de module dédié uniquement à la gestion des routes. Ils sont toujours rattachés à un autre module. Par exemple, Angular CLI a généré le module AppRoutingModule, qui définit les routes globales de notre application, et qui est importé dans le module racine AppModule. 
  • Les modules de fonctionnalités : Ils sont dédiés aux fonctionnalités spécifiques de votre application. Ils vous permettent de regrouper les éléments nécessaires pour implémenter une fonctionnalité : les services, les composants, les routes, etc. Par exemple dans notre cas, nous devrons surement créer un module de fonctionnalité dédié à la gestion des tâches : les afficher, en ajouter, effectuer des opérations de modification ou de suppression. Concrètement, un module de fonctionnalité sera un dossier dans votre projet, contenant tous les éléments nécessaires pour fonctionner : 
Organisation modulaire d’une application Angular.

Comme indiqué sur ce schéma, les modules de fonctionnalités sont suffisants pour structurer de petite ou moyenne application. Cependant, pour des applications plus importantes, je pense que nous devons voir d’autres pratiques qui nous vont nous servir pour mettre en place notre architecture. 

Sur ce schéma, je remarque qu’un module de fonctionnalité peut contenir d’autres modules de fonctionnalité. C’est bien ça ? 

Effectivement, un module de fonctionnalité peut tout à fait contenir d’autres modules du même type. D’ailleurs, cela va nous être utile pour découper notre application en deux parties : une partie publique accessible à tout le monde, et une partie privée réservée aux utilisateurs authentifiés. Pour chaque partie de notre application, nous créerons un module dédié. Cela permettra de regrouper plusieurs modules de fonctionnalités en grandes zones dans votre application : zone publique, zone protégée, etc. On pourrait même imaginer une zone administration par exemple, accessible uniquement par les personnes en charge de l’exploitation de l’application.

D’accord, ça a l’air pas mal tout ça. Et si je dois regrouper un composant ou service que j’utilise dans plusieurs modules différents ? 

Très bonne question ! Nous aurons rapidement du code commun entre plusieurs modules : les services d’accès aux données, les classes modélisant les entités de notre application, etc. C’est un problème récurrent sur lequel l’équipe Angular de chez Google a déjà réfléchi. Il est donc recommandé d’utiliser deux modules supplémentaires afin d’organiser son code d’une façon cohérente : le CoreModule et le SharedModule. Il s’agit de modules standards, mais qui respectent une certaine convention de nommage et de fonctionnement.

Le module CoreModule

Le rôle du CoreModule est d’alléger le module racine de votre application. Si vous avez déjà travaillé sur de petits projets, vous aurez sûrement remarqué que le module racine sert souvent de débarras : on y déclare tous nos composants, on ajoute les librairies tierces utilisées dans le projet, etc. Cependant, ce n’est pas son rôle. Son rôle est de démarrer votre application. L’équipe d’Angular a donc proposé plusieurs recommandations pour remédier au problème :

  • Créer un module nommé CoreModule dans votre projet, afin de décharger votre module racine des importations globales. 
  • Importer le CoreModule uniquement dans le module racine AppModule (n’importez jamais le CoreModule dans un autre module). 
  • Importer tous vos services dans le CoreModule. Ils seront accessibles dans tout votre projet, et il n’y aura qu’une seule instance de chaque service qui existera dans l’application. C’est très pratique si vous développez un service de partage des données entre plusieurs composants, car vous serez certains qu’il n’y a qu’un seul état possible dans votre application à un certain instant, étant donné qu’il existe une unique instance de votre service.
  • Vous pouvez également ajouter des composants globaux dans votre application. Par exemple, un composant représentant une icône de chargement, un pied de page, ou une barre de navigation. Pour faire simple, il s’agit des composants que vous n’utiliserez que dans le template racine app.component.html

Le module SharedModule

Le rôle du SharedModule est de centraliser l’importation des composants, des directives et des pipes, qui sont partagés par différents modules dans votre application. Il existe certains cas où vous hésiterez entre placer votre composant dans le CoreModule ou le SharedModule. Et il existe même des cas où il est toléré de mettre un service dans le SharedModule. Quel bazar, c’est à n’y rien comprendre au début ! 

Mais ne vous inquiétez pas, nous allons aborder calmement tous les cas possibles juste après. Je me suis assez creusé la tête là-dessus, je suis en mesure de vous donner des explications claires. 

  • Il faut créer un module nommé SharedModule dans votre projet, afin de centraliser les importations communes à plusieurs autres modules de votre application. 
  • Le SharedModule permet de regrouper les composants génériques, les directives et les pipes. 
  • Le SharedModule consiste uniquement à déclarer ces éléments communs, et à les réexporter ensuite. 
  • Le SharedModule peut également servir à rassembler les importations récurrentes d’autres modules, comme le CommonModule ou le FormsModule. Cela vous évite de réécrire les mêmes importations dans tous vos modules. Pour rappel, le CommonModule doit être importé dans tous vos modules exceptés le module racine. Il s’agit de la brique minimale dont un module Angular a besoin pour fonctionner. 
  • Le SharedModule doit être importé dans tous les autres modules qui en ont besoin, y compris le module racine AppModule si besoin. 

CoreModule ou SharedModule ?

Il y a d’éternels débats sur la toile, pour savoir où placer ses composants ou ses services. Les deux modules que nous venons de voir nous permettent de répondre à ces questions au sein d’une architecture robuste et réfléchie. Mais parfois, on ne sait pas si un élément doit être mis dans le CoreModule ou dans le SharedModule.  

Je pourrai vous donner la réponse académique. Mais cela vous donnera mal à la tête. Plutôt que vos nerfs lâchent avant que l’on ait écrit une seule ligne de code, je vous propose une version simplifiée de la répartition de vos éléments, afin de passer à la suite plus rapidement. 

  • Les services vont dans le CoreModule
  • Les composants, les directives et les pipes vont dans le SharedModule
  • Un module qui exporte au moins un composant, une directive ou un pipe doit être placé dans le SharedModule. Par exemple, le CommonModule doit faire partie du SharedModule, car il exporte entre autres les directives ngIf et ngFor
  • Sinon, ce module doit être placé dans le CoreModule. Par exemple, le HttpClientModule, car il n’exporte que des services. 

Bien, avec ces quelques principes en tête, vous comprendrez mieux les choix que l’on fera par la suite concernant la répartition entre ces deux modules. De toute façon, je justifierai chaque décision prise lors de nos développements, le moment venu. De plus, j’ai mis en annexes les explications détaillées concernant ce point. 

Je crois que vous en savez assez. On va tout de suite à l’implémentation de nos modules ! 

Rappelez-vous, l’utilisation du CoreModule et du SharedModule sont des recommandations, pas des obligations. Il s’agit simplement de bonnes pratiques éprouvées et recommandées par l’équipe d’Angular, mais vous êtes libre d’appliquer ou non leurs propositions. Vous pouvez très bien développer un module CoreModule dédié à la gestion des utilisateurs. Ce serait peut-être idiot de faire cela, mais ça ne lèvera pas d’erreurs dans votre application. Entre nous, je ne peux que vous recommander de respecter les conseils des ingénieurs qui ont développé notre framework préféré. Cela vous évite de réfléchir à des choses qui ont déjà été pensées mille fois, et de vous prendre la tête inutilement !

Implémentation de nos modules

Nous allons enfin implémenter nos modules. Je vous ai présenté les différents modules qui peuvent exister, ainsi que l’ensemble des pages disponibles dans notre application. Nous allons commencer par découper notre application en quatre modules principaux :

  • Le module racine AppModule, déjà généré pour nous par Angular CLI dans le fichier app.module.ts
  • Le module PublicModule qui va contenir toutes les pages publiques de notre application : la page d’accueil, de connexion et d’inscription. 
  • Le module ProtectedModule, qui contiendra notre application de productivité.  
  • Les modules CoreModule et SharedModule, que nous avons vu précédemment. 

Nous allons utiliser toute la puissance d’Angular CLI pour générer ces modules. Ouvrez donc un terminal de commandes à la racine de votre projet. La commande permettant de générer un module ressemble à quelque chose comme ça : 

ng generate module <nom_module>

On peut passer certaines options supplémentaires à cette commande, afin de mieux paramétrer le module qui est généré. Par exemple, l’option routing permet d’ajouter un module de gestion des routes au module généré, et l’option module permet de préciser un module parent. 

Voyons en pratique comment cela fonctionne. Respirez un grand coup, et exécutez les quatre commandes suivantes : 

ng generate module core --module=app 
ng generate module shared 
ng generate module public --routing --module=core 
ng generate module protected --routing --module=core

Si vous retournez dans votre éditeur de code, vous devriez voir quatre nouveaux dossiers dans le sous-dossier src/app (ce sous-dossier est l’emplacement dans lequel Angular CLI génère les nouveaux fichiers).  

Le CoreModule et le SharedModule sont composés d’un seul fichier. En revanche, les deux autres modules comprennent respectivement deux fichiers, car un module de gestion des routes a été généré en plus, comme nous l’avons demandé avec l’option routing :

L’architecture modulaire que je préconise pour un nouveau projet.

En effet, seul les modules Protected et Public auront des routes propres, pour la zone publique de l’application, et pour l’espace membre. 

De plus, le module SharedModule n’a pas de module parent pour le moment. Rappelez-vous qu’il servira de boîte à outil pour les modules qui en auront besoin, ce qui n’est pas le cas pour le moment. 

Au passage, je vous fais remarquer que ces quatre modules de départ sont assez génériques. Vous pouvez les générer pour vos prochains projets, même s’il ne s’agit pas du tout d’une application de productivité !  

Bonne pratique concernant le CoreModule

Avant de passer à la suite, j’aimerai que nous respections une bonne pratique recommandée par Google. Vous vous souvenez que le CoreModule ne doit être importé qu’une seule fois dans le AppModule. (Ne me dites pas que vous avez déjà oublié ! ) 

Pour le moment, rien ne nous empêche d’importer le CoreModule à plusieurs endroits dans l’application. Il est donc recommandé d’ajouter une petite vérification dans le constructeur du CoreModule, pour l’empêcher d’être instancié à différents endroits :

01 import { NgModule, Optional, SkipSelf } from ‘@angular/core’;  
02 
03 export class CoreModule { 
04  constructor(@Optional() @SkipSelf() parentModule: CoreModule) {
05   if (parentModule) {
06    throw new Error('CoreModule is already loaded.');
07   }
08  }
09 } 

Mais… Qu’est-ce que ce code barbare ? 

C’est une question légitime. Bon, déjà vous avez compris à quoi cela sert : s’assurer que le CoreModule n’est importé qu’une seule fois. En fait, vos interrogations doivent surtout porter sur ces deux nouvelles annotations, @Optional et @SkipSelf

La première annotation @Optional s’applique sur un paramètre de constructeur, et permet d’indiquer qu’une dépendance est optionnelle. Si la dépendance n’est pas renseignée, alors c’est la valeur null qui est injectée. 

La deuxième annotation @SkipSelf s’applique également sur un paramètre de constructeur. Elle indique au mécanisme d’injection de dépendance d’Angular que la résolution de cette dépendance doit commencer à partir de l’injecteur parent. Pour faire simple, le CoreModule ne peut être instancié qu’une fois, et depuis un module parent uniquement. 

Mais rassurez-vous, vous n’avez absolument pas besoin de vous prendre la tête sur ce code pour le moment. Sachez simplement qu’il s’agit d’une bonne pratique concernant le CoreModule. Nous aurons tout le temps d’aborder les questions techniques plus tard. 

Nous avons vu comment mettre en place une architecture modulaire, par-dessus le socle initial généré par Angular CLI. Cette architecture est suffisamment générique pour correspondre aux besoins de la plupart des nouvelles applications.

Où placer le composant de votre barre de navigation ? Et le service d’authentification ? Dans le prochain article, nous verrons comment construire une architecture robuste à partir de ce socle, afin d’avoir un code organisé et prêt pour les développements futurs ! 😉


Si vous n’avez pas la patience de chercher les articles sur le blog, je peux vous les envoyer dans l’ordre, directement dans votre boîte mail. En fait, cet article fait partie d’une série de 10 articles extraits de l’ouvrage Maîtriser Angular pour l’entreprise.