19 - Architecture & Multi modules
Introduction
Dans cette quête tu vas découvrir qu'il est possible de découper ton application Angular en plusieurs sous-parties : les modules.
Objectifs 🤓
✅ Comprendre comment architecturer un projet Angular pour le rendre plus simple et évolutif. ✅ Mettre en place ta première architecture multi-modulaire.
Avant de démarrer, assure-toi d'avoir terminé les quêtes suivantes👇
Principe fondateur de l'architecture Angular
Les "SOLID Design Principles"
Cela fait quelques quêtes maintenant que tu crées et manipules des composants. Ils constituent l'essence même de la philosophie du framework Angular :
Les composants permettent de découper ton application en un ensemble de petits éléments simples et réutilisables.
Cette philosophie s'inspire des principes fondateurs de conception en programmation orientée objet, qui ont pour but de rendre tes conceptions plus compréhensibles, flexibles et surtout plus maintenables. Ils sont au nombre de 5 et sont communément appelés S.O.L.I.D. Design Principles. Le but ici n'est pas de te les détailler, mais plus de te sensibiliser à leur philosophie et leur approche clean code.
👉 Les 2 ressources ci-dessous requièrent de bonnes connaissances en POO, donc ne t'inquiète pas si tu ne comprends pas encore tout. En revanche, assure-toi de les mettre en favoris ! 🤓
Couche de logique, composants pages, composants intelligents et composants d'affichages
Notre architecture Angular va donc s'inspirer des SOLID Design Principles. Pour cela, nous allons introduire 4 notions :
- Couche de logique
- Composants pages
- Composants intelligents
- Composants d'affichages
Attends, il faut faire une distinction entre 3 catégories de composants maintenant ? 🤯
Et bien oui ! Ou du moins, si tu veux t'assurer de créer une architecture Angular la plus flexible et maintenable possible.
Voici un schéma qui te présente l'architecture fondamentale d'un module Angular flexible :

Pas de panique, nous allons détailler tous les éléments ensemble 🤓
💡 L'idée est la suivante :
- Une
couche de logique(ou business layer) s'occupe de traiter toute la logique métier : récupération des données d'une API, algorithmes de formatage de données ou de tri, fusion de plusieurs flux de données... en bref, tout ce qui est en lien avec la partie métier de l'application et qui demande un traitement "complexe".
Par exemple, ça pourrait être une classe qui s'occupe de récupérer les données d'une API sur les animaux, et de ranger les animaux par ordre alphabétique. On utilise généralement un
servicepour créer une couche de logique.
- Cette couche de logique est connectée à un (ou plusieurs)
composants intelligents(ou smart component). On dit d'un composant qu'il est intelligent (smart) lorsqu'il a connaissance de la logique métier (business logic). Autrement dit, un smart component est un composant relié à un service par injection de dépendances (dependency injection ou DI).
Petit rappel sur le principe d'injection de dépendance. C'est un sujet important mais aussi complexe. N'hésite pas à revenir dessus plusieurs fois durant ces prochaines semaines !
-
Chaque composant intelligent appartient à un
composant page(view component) qui est chargé via le routing. Pour rappel, pour naviguer vers une page de ton application, tu définies une route et un composant lié à cette route. Lorsque tu navigues sur la route, tu charges le composant lié. Ce "composant lié" est uncomposant page. Il est très simple et ne contient pas de logique (pas de typescript). Son seul rôle est d'appeler lescomposants intelligentsdans sa partie HTML. -
Chaque
composant intelligentrécupère les données depuis la couche logique. C'est son seul et unique rôle (ou objectif). Ce n'est pas à lui d'afficher les données : il délègue cette tâche auxcomposants d'affichagesou dumb components en leur envoyant en @Input() les données que ces derniers sont chargés d'afficher.
👉 Si on reprend notre exemple précédent :
- Notre
couche de logique(un service) détient les données de l'API d'animaux triés par ordre alphabétique.- Notre
composant pageest chargé en routing et appelle notrecomposant intelligent.- Notre
composant intelligentest connecté à notrecouche de logiquepar injection de dépendance et récupère les données triées. Il appelle uncomposant d'affichageet lui envoie les données par @Input()- Notre
composant d'affichagese charge d'afficher par binding les données reçues en @Input() (ça serait par exemple unecard) et, si besoin, émet des événéments aucomposant intelligentpar @Output()
Si tu reprends le schéma de tout à l'heure, il devrait te paraître un peu plus clair maintenant 😌
Tous ces termes techniques peuvent te sembler un petit peu effrayant et c'est normal ! Mais tu finiras par vite t'y habituer. Retiens surtout que respecter ce modèle de conception change vraiment la vie lorsque ton application prend de l'ampleur !
Architecture des dossiers au sein d'un module
Tu sais maintenant qu'il existe une hiérarchie entre les fichiers d'un même module : la couche de logique envoie ses données au composant intelligent qui délègue l'affichage à son (ses) composant(s) d'affichage(s). C'est une bonne première étape, mais dès lors que ton projet prend de l'envergure, tu crées beaucoup de composants. Il faut donc que tu puisses les organiser correctement pour t'y retrouver sans problème 🐙.
👉 Pour cela, tu vas devoir segmenter ton module en plusieurs sous-dossiers :
- Un dossier
shareddans lequel tu ajoutes ta business layer (tes services) - Un dossier
componentsqui comporte 2 sous-dossiers :- Un sous-dossier
featurequi comporte tous tes smart components - Un sous-dossier
uiqui comporte tes dumb components
- Un sous-dossier
- Un dossier
pagesqui regroupe tous tes view components - Un dossier
modelsqui regroupe toutes les classes/interfaces dont ton module a besoin
Voici ce que ça donne en image :

Cette organisation peut sembler très militaire, mais elle est fondamentale pour charpenter des projets Angular scalables et évolutifs dans lesquels il est agréable de naviguer en tant que développeur.
👉 Tu viens donc de voir comment s'architecture UN module Angular. Mais ce n'est pas encore tout à fait fini :
Il te reste une dernière étape : diviser ton application en plusieurs modules 🤓
Créer un projet multi-modulaire
Oui mais pourquoi ?
La création de modules dans ton application n'est pas très difficile. Le plus délicat, c'est de bien réfléchir à leurs rôles respectifs. C'est quelque chose qui vient avec l'expérience. Néanmoins, tu peux retenir que l'idée est de créer un module par fonctionnalité principale.
Dans la plupart des applications, tu as généralement des fonctionnalités principales comme : authentification, paiement, espace membre, espace administrateur, etc... (pense à Odyssey par exemple).
👉 Avec Angular, on créerait donc un module par fonctionnalité principale :
-
Un module pour la partie authentification (inscription/connexion)
-
Un module pour la partie paiement
-
Un module pour l'espace membre
-
Un module pour l'espace adminisateur
-
etc...
Et tu peux redécouper chaque module en sous-modules ! (c'est là tout l'intérêt !). Si on reprend notre exemple, le module authentification (auth.module.ts) gère à la fois l'inscription et la connexion. Tu créerais alors :
- Un sous-module pour l'inscription (
registration.module.ts) - Un sous-module pour la connexion (
connexion.module.ts)
Pour revenir à un exemple plus simple, admettons que tu viennes de créer un nouveau projet et que tu décides de créer 2 modules : account et auth. Voici ce que donnerait ton architecture Angular :

✅ Cette nouvelle organisation permet de facilement comprendre la responsabilité de chaque module et te repérer dans ton code. C'est aussi pour une question de performances : tu peux demander à Angular de ne charger ton module que lorsque tu navigues dessus. On parle alors de lazy loading (chargement fainéant).
Et comment je fais ça ?
Pour créer un projet multi-modulaire, nous allons repartir d'un projet vierge. Tu peux par exemple l'appeler multimod.
Création et architecture du premier module
Jusqu'à maintenant, lorsque tu crées un composant avec le CLI Angular (ng generate component "nom-du-composant"), Angular ajoute automatiquement ce nouveau composant dans le app.module.ts.
Dans un contexte multi-modulaire, le composant sera ajouté dans le module le plus proche (module du dossier le plus proche).
On fera donc attention à créer les sous modules avant de créer leurs composants !
👉 Si on reprend notre exemple :
-
Place toi à la racine de ton application angular (src/app) et créé un nouveau dossier que tu appelleras
modules. Tu viendras y ranger tous tes modules. -
Dans ton nouveau projet, génère un nouveau module (par exemple un module qui va permettre de gérer un compte utilisateur. On l'appellera
account). Pour cela, tu peux utiliser la commande de ton cli :ng g module modules/account --routing. Cela aura pour conséquence de :
- Créer le module
account.module.tsdans le dossiermodules- Créer un fichier de routing
account.routing.module.tspour gérer le routing relatif à ce module
✅ Cela t'indique que le module a bien été créé. Mais avant de créer le moindre composant, tu vas devoir architecturer ton module comme ce que tu as vu plus haut. Il faut donc créer les dossiers shared, components, feature, ui, pages, models à l'intérieur de ce nouveau module.
Une fois que c'est fait, tu souhaites maintenant créer un composant page dans ton module account :
- Rends-toi avec ton terminal dans le dossier pages de ton module account
- Créé un nouveau composant
ng g component account-home. Cela aura pour conséquence de créer le componsant AccountHomeComponent.
Première étape terminée ! 🥳 Il ne te reste plus qu'à savoir comment charger en lazy loading un composant appartenant à un module.
Les performances & le LazyLoading
Au lancement de ton application, tous les éléments sont chargés automatiquement. Pourtant, la première fois qu'un utilisateur vient sur ton application, il est inutile de charger le module account puisque la première chose qu'il verra sera probablement la partie "vitrine" du site, ou bien une autre page telle que la page de connexion ou la page d'inscription.
Pour gérer ce scénario, Angular met à notre disposition le "Lazy Loading" 😎
En développement, la notion de Lazy/Eager revient souvent, c'est l'opposition entre "Charger le minimum (fainéant) / Tout charger automatiquement (avide)". Lazy sera toujours plus performant, mais parfois moins pratique à implémenter.
Quand on veut mettre en place un système lazy loadé, cela signifie donc qu'on charge les choses "à la dernière minute" ou plutôt juste avant d'en avoir besoin. Pour que ce mécanisme fonctionne, il faut que :
-
Nous ayons un routing.module par module. Par exemple, il faut avoir le fichier
account.routing.module.tspour le moduleaccount. -
Que les routes n'utilisent plus le Eager loading (que tu as mis en place dans la quête sur le routing) mais plutôt le Lazy loading.
Et pour implémenter le lazy loading, voici à quoi doit ressembler ta route dans ton app.routing.ts :
// Dans le fichier app-routing.module.ts
{ path: 'account', loadChildren: () => import('./modules/account/account.module').then(m => m.AccountModule) }
Si l'on décortique :
pathest une partie de ton URL. Lorsque ton router reçoit stricto sensu cette URL, il déclenche la méthode loadChildren.loadChildrenest une méthode qui importe ton module de manière asynchrone. Lorsque l'import est terminé, il charge ensuite le module. Ne te formalise pas sur la syntaxe. Sache il s'agit simplement d'une Promise en Javascript.
Tu y es presque ! Dernière étape : configurer le routing de ton nouveau module.
En reprenant l'exemple ci-dessus, lorsque tu navigues vers /account, tu charges le module account. Mais pour le moment, ça ne fait pas grand chose : ce nouveau module doit lui aussi configurer son propre routing afin de charger ses propres composants. C'est pour cette raison que lorsque tu as créé ton module tout à l'heure, tu as ajouté --routing à la suite de ng g module modules/account.
👉 L'option --routing permet de créer un fichier de routing propre à son module.
Tu as donc, si tout va bien, un fichier account.routing.module.ts. Et bien c'est là que tu vas pouvoir déclarer les routes pour charger tes composants comme d'habitude (en Eager Loading, puisqu'ici il ne s'agit pas de charger un autre module, mais simplement de charger des componsants de ce module). Tu vas donc pouvoir charger le composant account-home qui appartient à account.module.ts que tu as créés tout à l'heure :
// Dans le fichier account-routing.module.ts
{ path: 'home', component: AccountHomeComponent }
Enfin, pour accéder à ton composant AccountHomeComponent, tu vas devoir entrer l'URL /account/home. Celui-ci est une composition :
-
Du path présent dans le routing module parent (dans notre cas, le path "account" est présent dans le
app-routing.module) -
Du path présent dans le routing module lazy loadé (dans notre cas, le path "home" est présent pour charger AccountHomeComponent dans
account.routing.module.ts
✅ Le chemin final est donc "/account" + "/home" (c'est à dire: /account/home) ! Et tu es maintenant prêt·e pour le challenge 🦹♂️
Challenge
Gérer des utilisateurs
Tu vas devoir créer un projet multi-modulaire ! Pour cela, commence par créer un nouveau projet Angular dans lequel tu crées un fichier users.json que tu placeras dans assets/json. Ce fichier simulera la réponse d'une API. Voici les données à copier dedans :
Voir les données |||jsx||||||0|||Cacher
[
{
"id": 1,
"username": "johnDoe",
"firstname": "John",
"lastname": "Doe",
"email": "johndoe@example.com"
},
{
"id": 2,
"username": "janeSmith",
"firstname": "Jane",
"lastname": "Smith",
"email": "janesmith@example.com"
},
{
"id": 3,
"username": "bobJohnson",
"firstname": "Bob",
"lastname": "Johnson",
"email": "bobjohnson@example.com"
},
{
"id": 4,
"username": "saraWilson",
"firstname": "Sara",
"lastname": "Wilson",
"email": "sarawilson@example.com"
},
{
"id": 5,
"username": "davidLee",
"firstname": "David",
"lastname": "Lee",
"email": "davidlee@example.com"
}
]
Ton application devra contenir :
- Un module app, le module initial généré par Angular qui comporte :
- Une classe "User" qui a pour propriétés : "username", "firstname", "lastname", "email".
- une couche de logique (service) qui récupère en requête HTTP le fichier
users.json.
Ces deux éléments sont dans le module racine app car ils sont transversaux à tous tes modules. Les règles de dossiers pour organiser un module s'appliquent également au module app.
-
Un module admin qui comporte :
- un composant page chargé par l'URL
/admin/home. - un composant intelligent qui récupère les données de la couche de logique.
- un composant d'affichage qui affiche chaque
userdeusers.
- un composant page chargé par l'URL
-
Un module account qui comporte :
- un composant page chargé par l'URL
/account/home. - un composant intelligent qui récupère les données de la couche de logique.
- un composant d'affichage qui affiche un
useraléatoire.
- un composant page chargé par l'URL
BONUS 1 : le composant d'affichage du module account affiche non pas un user aléatoire mais un user dont l'ID correspond à un paramètre d'URL.
BONUS 2 : Créer un module shared-components dans lequel tu déclares les composants réutiltisés à travers tes modules.
Critères de validation
- Il y au total 3 modules :
app,admin,account. - La classe et le service sont dans le module
app - Les modules
adminetaccountsont chargés en lazyloading. - La liste des utilisateurs est affichée lorsque tu navigues sur
/admin/home. - Un utilisateur est affiché lorsque tu navigues sur
/account/home. - Il y a 2 routes lazy loadées dans le
app-routing.module.