- cohérence : simplicité (pour les petites applis) et évolutivité (pour les grosses apps) ;
- réutilisabilité dans plusieurs projets ;
- optimisation (cohérent avec ou sans chargement à la demande) ;
- testabilité.
Angular modules
Qu’est-ce qu’un NgModule ?
Le but d’un NgModule est simplement de regrouper les composants et/ou services qui vont ensemble. Rien de plus, rien de moins. Vous pouvez donc les comparer à un package Java ou à un namespace en PHP ou C#. La seule question est : comment choisir ce qui va ensemble ?Types de modules Angular
Vous pouvez créer 3 principaux types de NgModules :- des modules de pages ;
- des modules de services globaux
- des modules de composants d’UI réutilisables.
Modules de pages
Les modules de pages sont les modules avec routing. Ils servent à séparer et organiser les différentes parties de votre application. Ils sont chargés une seule fois, soit dans l’AppModule, soit à la demande (lazy-loading). Par exemple, vous pourriez avoir un AccountModule pour vos pages d’inscription, de connexion et de déconnexion ; puis un HeroesModule pour les pages de la liste des héros et du détail d’un héros ; etc. Ces modules contiennent 3 choses :- /shared : services et interfaces,
- /pages : composants routés,
- /components : purs composants de présentation.
Shared services for pages
Pour afficher une page, vous avez d’abord besoin de données. Et voilà le service :
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@Injectable({ providedIn: 'root' }) | |
export class SomeService { | |
constructor(protected http: HttpClient) {} | |
getData() { | |
return this.http.get<SomeData>('/path/to/api'); | |
} | |
} |
Pages : composants routés
Un composant page injecte seulement le service, et l’utilise pour récupérer les données. Vous pourriez afficher les données directement dans le template du composant, mais il ne faut pas : les données doivent être transférées à autre composant via un attribut.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@Component({ | |
template: `<app-presentation *ngIf="data" [data]="data"></app-presentation>` | |
}) | |
export class PageComponent { | |
data: SomeData; | |
constructor(protected someService: SomeService) {} | |
ngOnInit() { | |
this.someService.getData().subscribe((data) => { | |
this.data = data; | |
}); | |
} | |
} |
Composants de présentation
Un composant de présentation récupère simplement les données transférées avec le décorateur Input, et les affiche dans la template.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@Component({ | |
selector: 'app-presentation', | |
template: `<h1>{{data.title}}</h1>` | |
}) | |
export class PresentationComponent { | |
@Input() data: SomeData; | |
} |
Est-ce du MVx ?
D’un point de vue théorique, non. Mais si vous venez du monde du back-end et que cela vous aide d’un point de vue pratique, vous pouvez comparer :- les services seraient les Modèles ;
- les composants de présentation seraient les Vues ;
- les composants pages seraient les Contrôleurs / Presenters / ViewModels (choisissez ce dont vous avez l’habitude).
- la réutilisabilité : les composants de présentation sont réutilisables dans plusieurs pages ;
- l’optimisation : la détection de changement des composants de présentation peut être optimisée ;
- la testabilité : les tests unitaires sont possibles sur les composants de présentation (si vous n’avez pas séparé les rôles, oubliez les tests, ça sera juste un enfer).
En résumé
Voici un example de module de pages :
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@NgModule({ | |
imports: [CommonModule, MatCardModule, PagesRoutingModule], | |
declarations: [PageComponent, PresentationComponent] | |
}) | |
export class PagesModule {} |
Modules de services globaux
Les modules de services globaux sont des modules avec des services dont vous avez besoin partout dans l’application. Comme les services ont généralement une portée globale, ces modules sont chargés une seule fois dans l’AppModule, et les services sont ensuite accessibles partout (y compris dans les modules chargés à la demande). Vous utilisez probablement au moins l’un d’entre eux : le module HttpClient. Et vous aurez vite besoin des vôtres. Un cas très classique est l’AuthModule, pour stocker le statut de connexion de l’utilisateur (cette donnée devant être accessible partout dans l’app) et sauvegarder le token. Note : depuis Angular 6, il n’est plus nécessaire d’avoir un NgModule pour les services, qui désormais s’auto-fournissent. Cela ne change rien à l’architecture présentée ici.Point d’entrée
Vous pouvez facilement réutiliser les modules de services globaux dans différents projets, à condition de faire attention de n’avoir aucune dépendance particulière (pas de code spécifique à votre app ou à une librairie d’UI particulière), et si vous séparez bien chaque fonctionnalité dans différents modules (ne mettez pas tous les services dans un seul module global). Comme un tel module sera utilisé depuis l’extérieur, il est conseillé de créer un point d’entrée, dans lequel vous exportez le NgModule, les services et peut-être aussi des interfaces et de tokens d’injection.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export { SomeService } from './some.service'; | |
export { SomeModule } from './some.module'; |
Dois-je faire un CoreModule ?
Ce n’est pas nécessaire. La documentation suggère de faire un CoreModule pour les services globaux. Vous pouvez tout à fait les rassembler dans un répertoire /core/, mais comme mentionné ci-dessus, assurez-vous de séparer chaque fonctionnalité. Il ne faut pas mettre tous les services globaux dans un seul CoreModule, sinon vous ne pourrez pas réutiliser chaque fonctionnalité dans un autre projet.En résumé
Voici un exemple de module de services globaux :
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@NgModule({ | |
providers: [SomeService] | |
}) | |
export class SomeModule {} |
Modules de composants réutilisables
Les modules de composants réutilisables sont les modules de composants d’interface que vous souhaitez réutiliser dans plusieurs projets. Comme les composants sont en portée locale, ces modules doivent être chargés dans chaque module de pages qui en a besoin. Vous en utilisez probablement, comme Material, NgBootstrap ou PrimeNg. Vous pouvez aussi faire les vôtres.Comment récupérer les données ?
Les composants d’UI sont de purs composants de présentation. Ils fonctionnent donc exactement comme ceux des modules de pages (voir ci-dessus) : les données doivent provenir du décorateur Input (parfois aussi de <ng-content> dans des cas avancés) :
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@Component({ | |
selector: 'ui-carousel' | |
}) | |
export class CarouselComponent { | |
@Input() delay = 5000; | |
} |
Composants publics et privés
Les composants étant en portée locale, n’oubliez pas de les exporter dans le NgModule. Vous devez seulement exporter les composants publics : les sous-composants internes peuvent rester privés.Directives et pipes
Un module d’UI peut aussi contenir des directives ou des filtres. Il en va de même que pour les composants : ils doivent être exportés s’ils sont publics.Services privés
Des services dans des modules de composants peuvent être pertinents pour manipuler les données, s’ils ne contiennent rien de spécifique. Mais soyez sûr de les fournir à l’intérieur du composant, pour qu’ils soient en portée locale/visibilité privée, et surtout pas dans le NgModule :
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@Component({ | |
selector: 'some-ui', | |
providers: [LocalService] | |
}) | |
export class SomeUiComponent {} |
Services publics
Comment faire si votre module d’UI doit aussi fournir des services publics, liés à votre composant ? Cela doit être évité autant que possible, mais c’est parfois nécessaire. Vous devrez alors fournir ces services publics dans le NgModule. Mais comme ce module sera chargé plusieurs fois à cause de la portée des composants, cela va poser un problème pour les services. Il faut alors ajouter un code spécifique pour chaque service public, pour empêcher qu’il soit chargé plusieurs fois. Il serait trop long de l’expliquer ici, mais c’est une bonne pratique (utilisé par exemple dans Material). Remplacez simplement SomeService par le nom de votre classe.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export function SOME_SERVICE_FACTORY(parentService: SomeService) { | |
return parentService || new SomeService(); | |
} | |
@NgModule({ | |
providers: [{ | |
provide: SomeService, | |
deps: [[new Optional(), new SkipSelf(), SomeService]], | |
useFactory: SOME_SERVICE_FACTORY | |
}] | |
}) | |
export class UiModule {} |
Point d’entrée
Les modules de composants d’UI sont réutilisables dans différents projets. Comme un tel module sera utilisé depuis l’extérieur, il est conseillé de créer un point d’entrée, dans lequel vous exportez le NgModule, les composants publics/exportés (et peut-être aussi des directives, des pipes, des services publics, des interfaces et des tokens d’injection).
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export { SomeUiComponent } from './some-ui/some-ui.component'; | |
export { UiModule } from './ui.module'; |
Dois-je faire un SharedModule ?
Non. La documentation suggère de faire un SharedModule, pour factoriser tous les modules de composants dans un seul module. Mais je vais plaider contre la documentation sur ce point. Le problème est que chaque module dans lequel vous allez importer le SharedModule va devenir spécifique à votre application, et ne sera pas donc réutilisable dans un autre projet. Il est normal d’avoir à importer les dépendances chaque fois dont on en a besoin. Avec les outils actuels comme les imports automatiques dans VS Code, cela n’est plus un souci. Vous pouvez en revanche tout à fait rassembler les modules de composants réutilisables dans un répertoire /ui/ (ne l’appelez pas /shared/, cela porterait à confusion avec les services qui sont également partagés).En résumé
Voici un exemple de module de composants d’interface réutilisables :
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@NgModule({ | |
imports: [CommonModule], | |
declarations: [PublicComponent, PrivateComponent], | |
exports: [PublicComponent] | |
}) | |
export class UiModule {} |
Conclusion
En suivant ces étapes :- vous aurez une architecture cohérente, dans les petites ou grosses apps, avec ou sans lazy-loading ;
- vos modules de services globaux et ceux de composants d’UI sont prêts à être transformés en librairies, réutilisables facilement dans d’autres projets ;
- vous pourrez faire des tests unitaires sans vous arracher les cheveux.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
app/ | |
|- app.module.ts | |
|- app-routing.module.ts | |
|- core/ | |
|- auth/ | |
|- auth.module.ts (optional since Angular 6) | |
|- auth.service.ts | |
|- index.ts | |
|- othermoduleofglobalservice/ | |
|- ui/ | |
|- carousel/ | |
|- carousel.module.ts | |
|- index.ts | |
|- carousel/ | |
|- carousel.component.ts | |
|- carousel.component.css | |
|- othermoduleofreusablecomponents/ | |
|- heroes/ | |
|- heroes.module.ts | |
|- heroes-routing.module.ts | |
|- shared/ | |
|- heroes.service.ts | |
|- hero.ts | |
|- pages/ | |
|- heroes/ | |
|- heroes.component.ts | |
|- heroes.component.css | |
|- hero/ | |
|- hero.component.ts | |
|- hero.component.css | |
|- components/ | |
|- heroes-list/ | |
|- heroes-list.component.ts | |
|- heroes-list.component.css | |
|- hero-details/ | |
|- hero-details.component.ts | |
|- hero-details.component.css | |
|- othermoduleofpages/ |
Devenez un Pro!
Cet article vous a aidé ? Et vous voulez soutenir mes contributions open source (avec notamment un outil utilisé par 600 000 développeurs) ? Faites-vous une faveur à vous-même : Schematics Pro est un outil d’automatisation de code pour Angular, React, Vue et tous les autres frameworks JavaScript, qui vous fait gagner en productivité et vous aide à vous assurer du suivi des bonnes pratiques.
En savoir plus sur Schematics Pro
Découvrez notre formation Angular, animée par l’auteur de cet article !