Comprendre les modules Angular (NgModule)

Publié le

La première structure de base que vous croiserez dans Angular, ce sont les NgModules. Mais c’est aussi la plus subtile et la plus complexe, à cause de différences de portée.

(Cet article est disponible en Anglais sur Medium.

La documentation officielle a mis à disposition toute une foire aux questions à propos des NgModules, et malgré tout cela reste compliqué à enseigner lors des formations que j’anime, cela perturbe les débutants, alors j’ai décidé de tout résumer dans cet article.

Pourquoi des NgModules ?

Angular CLI le fait automatiquement, mais la première chose qu’il faut faire dans Angular est de charger le NgModule racine :

platformBrowserDynamic().bootstrapModule(AppModule);
view raw main.ts hosted with ❤ by GitHub

Un NgModule sert à enregistrer tout ce que vous créez dans Angular, et les grouper ensemble (un peu comme un package Java ou un namespace en PHP / C#).

Il y a deux types de structures principales :

  • « declarations » pour les choses que vous utilisez dans vos templates : principalement les composants (~ vues : les classes qui affichent les données), mais aussi des directives et les filtres (pipes) ;
  • « providers » pour les services (~ modèles : les classes récupérant et traitant les données).

import { NgModule } from '@angular/core';
import { SomeComponent } from './some.component';
import { SomeDirective } from './some.directive';
import { SomePipe } from './some.pipe';
import { SomeService } from './shared/some.service';
@NgModule({
declarations: [SomeComponent, SomeDirective, SomePipe],
providers: [SomeService]
})
export class SomeModule {}
view raw some.module.ts hosted with ❤ by GitHub

Note : depuis Angular 6, il n’est plus nécessaire de déclarer les services dans un NgModule.

NgModule et portée / visibilité

La confusion commence avec le fait que les déclarations et les providers n’ont pas la même portée :

  • les déclarations / composants sont en portée locale (~ visibilité privée),
  • les providers / services sont (généralement) en portée globale (~ visibilité publique).

Cela signifie que les composants que vous déclarez seront seulement utilisables dans le module en cours. Si vous en avez besoin à l’extérieur, dans un autre module, vous devez les exporter :

import { NgModule } from '@angular/core';
import { SomeComponent } from './some.component';
import { SomeDirective } from './some.directive';
import { SomePipe } from './some.pipe';
@NgModule({
declarations: [SomeComponent, SomeDirective, SomePipe],
exports: [SomeComponent, SomeDirective, SomePipe]
})
export class SomeModule {}
view raw some.module.ts hosted with ❤ by GitHub

A l’inverse, les services que vous fournissez sont généralement disponibles / injectables n’importe où dans votre application, dans n’importe quel module.

Quand importer un NgModule ?

La différence de portée entre les composants et les services est importante à connaître, mais jusque là ça va encore. Les choses se compliquent car évidemment, comme dans n’importe quel framework ou application, vous n’allez pas avoir qu’un seul module, mais plusieurs. Angular lui-même est décomposé en plusieurs modules différents (core, common, http, etc.).

La deuxième chose essentielle que vous ferez dans un module Angular est donc d’importer les autres NgModules dont vous avez besoin.

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { HttpClientModule } from '@angular/common/http';
import { FeatureModule } from '../feature/feature.module';
@NgModule({
imports: [CommonModule, HttpClientModule, FeatureModule]
})
export class SomeModule {}
view raw some.module.ts hosted with ❤ by GitHub

Le problème, c’est que vous devez savoir pourquoi vous importer ces autres modules :

  • est-ce pour récupérer des composants, des directives ou des filtres (pipes) ?
  • ou est-ce pour récupérer des services ?

Pourquoi est-ce important ? Car étant donné la différence de portée entre les composants et les services :

  • si le module est importé pour des composants, il faudra le réimporter dans chaque module qui en a besoin ;
  • si le module est importé pour des services, il faudra l’importer une seule fois, dans le premier AppModule,

Sinon, vous aurez des erreurs liées à des composants non disponibles, car vous avez oublié d’importer à nouveau leur module.

Ou si vous importez un module de services plusieurs fois, cela peut générer des erreurs dans des situations avancées comme le lazy-loading.

Quand importer les principaux modules Angular ?

Une bonne connaissance des modules Angular est donc nécessaire, pour savoir combien de fois vous avez besoin de les importer. Voici un résumé.

Modules à importer à chaque fois que vous en avez besoin :

  • CommonModule (templating Angular : bindings, *ngIf, *ngFor…), sauf dans le premier AppModule, car il est déjà inclus dans le BrowserModule
  • FormsModule / ReactiveFormsModule
  • MatXModule et autres modules d’UI (comme PrimeNg)
  • tout autre module vous donnant des composants, directives ou filtres (pipes)

Modules à importer seulement une fois

  • HttpClientModule
  • BrowserAnimationsModule ou NoopAnimationsModule
  • tout autre module vous fournissant uniquement des services.

C’est pour ces raisons qu’Angular CLI importe automatiquement le CommonModule quand vous créez un nouveau module.

NgModules mixtes

Cela peut encore se compliquer : comment gérer un module qui fournit à la fois des composants et des services ?

Vous en connaissez un : le RouterModule. Il vous donne accès à un composant (<router-outlet>) et une directive (routerLink), mais aussi à des services (ActivatedRoute pour récupérer les paramètres de l’URL, Router pour naviguer, etc.).

Heureusement, cela est géré en interne par le module. Angular CLI pré-génère automatiquement les fichiers de routing, mais vous aurez peut-être remarqué qu’il y a une légère différence entre le routing du premier AppModule et celui des sous-modules.

Pour le AppModule :

RouterModule.forRoot(routes)

Pour les sous-modules :

RouterModule.forChild(routes)

Pourquoi ? Car la première fois dans l’AppModule, forRoot() va fournir les composants et les services du router. Mais les fois suivantes dans les sous-modules, forChild() fournira seulement les composants (sans refournir à nouveau les services, ce qui ne serait pas bon).

Lazy-loaded modules

Dernière complication : si vous chargez des modules à la demande (lazy-loading), ce qui est désormais facile via Angular CLI.

const routes: Routes = [
{ path: 'admin', loadChildren: './admin/admin.module#AdminModule' }
];

Comme il s’agira d’un bundle et d’un module différents, chargés seulement sur demande (par défaut), cela ne fera pas tout à fait partie de l’espace global de votre application.

Pour les composants, cela ne change rien : vous devez importer à nouveau le CommonModule ou votre SharedModule, comme dans n’importe quel sous-module.

En revanche, pour les services, il y a une différence :

  • vous aurez toujours accès aux services déjà fournis par l’application (comme Http ou vos propres services) ;
  • par contre, les services fournis dans votre module chargé à la demande seront seulement disponibles dans la portée de ce module-ci, et non pas dans toute l’application.

Conclusion

Votre arbre d’imports (qui n’est pas exactement le même que vos répertoires) devrait ressembler à ça :

AppModule
|- BrowserModule (includes CommonModule)
|- AppRoutingModule
|- GlobalServicesModules (HTTP, auth…)
|- HeroesModule
|- CommonModule
|- UIModules (Material, forms, your own UI…)
|- HeroesRoutingModule
|- AccountModule
|- CommonModule
|- UIModules (Material, forms, your own UI…)
|- AccountRoutingModule
view raw imports.txt hosted with ❤ by GitHub

Architecture d’un projet Angular

Vous voulez savoir comment structurer vos propres modules dans une application Angular ? Lisez l’article suivant.

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 ma formation Angular