• 8 hours
  • Medium

Free online content available in this course.

course.header.alt.is_video

course.header.alt.is_certifying

Got it!

Last updated on 3/31/22

Facilitez la maintenance avec les modules

Une application complète Angular peut comporter des centaines de components, des dizaines de services, de directives, de pipes – garder tous ces éléments à la racine du dossier  app  et importés au même endroit devient vite un enfer d'illisibilité et de maintenabilité.

Angular nous propose un outil extraordinaire pour améliorer l'architecture et les performances de nos applications : les modules.

Un module permet de déclarer et de regrouper des components, services, directives, pipes, et même d'autres modules.

Diviser votre application en modules permettra une meilleure lisibilité, une maintenance plus simple, et des performances accrues.

Dans ce chapitre, vous allez découvrir les principaux types de modules, et séparer l'application Snapface en modules.

Modularisez votre architecture

Il existe trois types principaux de modules :

  • feature modules : ces modules encapsulent tous les éléments d'un "feature" de votre application – ce qui définit un "feature" peut parfois être flou, mais dans l'exemple de l'application Snapface, on pourrait distinguer deux features : la landing page, et toute la gestion des FaceSnaps ;

  • core modules : ce type de module contient tout ce que l'on importe une seule fois dans application (les core modules sont eux-mêmes importés une seule fois, la majorité du temps dans AppModule) – les services, les modèles, et les intercepteurs par exemple, ou des components comme HeaderComponent dans Snapface ;

  • shared modules : ce sont des modules qui regroupent des éléments utilisés à plusieurs endroits de l'application – il importe et déclare tous ces éléments et les réexporte, permettant à n'importe quel module de tout importer d'un coup.

Dans Snapface, il n'y a pas d'éléments partagés qui mériteraient la création d'un SharedModule, donc vous allez créer uniquement un CoreModule et des feature modules pour les FaceSnaps et la LandingPage !

CoreModule

Pour Snapface, vous allez commencer par créer un CoreModule. Vous pouvez utiliser le CLI à partir de la racine du projet :

ng generate module core

ou, en raccourci :

ng g m core

Cette commande génère un dossier core avec un fichier  core.module.ts  .  Ce fichier contient la configuration par défaut d'un module Angular, donc jetons-y un oeil :

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';

@NgModule({
  declarations: [],
  imports: [
    CommonModule
  ]
})
export class CoreModule { }

On voit que, par défaut, Angular importe CommonModule. Si jamais votre module doit déclarer des components (comme CoreModule va déclarer HeaderComponent), il faut qu'il importe CommonModule.

Ce module va réunir le cœur de votre application : les modèles, les services, les intercepteurs, et HeaderComponent.

Pour les modèles et services, il n'y a pas besoin de les importer ouvertement dans CoreModule : les modèles ne sont pas déclarés, et les services se déclarent eux-mêmes avec leurs décorateurs  @Injectable() . Vous pouvez donc vous contenter de déplacer les dossiers  models  et  services  dans le dossier  core  .

Certains IDE (comme WebStorm) permettent de modifier automatiquement les chemins d'import lorsque vous déplacez des fichiers. Si ce n'est pas le cas pour vous, il faudra les modifier manuellement. Par exemple, dans FaceSnapListComponent, l'import du service devient :

import { FaceSnapsService } from '../core/services/face-snaps.service';

Ensuite, pour les intercepteurs, déplacez le dossier  interceptors  dans le dossier  core  , puis faites dans CoreModule ce que vous avez fait dans AppModule :

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { httpInterceptorProviders } from './interceptors';

@NgModule({
  declarations: [],
  imports: [
    CommonModule
  ],
  providers: [
    httpInterceptorProviders
  ]
})
export class CoreModule { }

Vous pouvez maintenant supprimer  httpInterceptorProviders  de AppModule et y importer le CoreModule que vous venez de créer :

// ...
import { CoreModule } from './core/core.module';

@NgModule({
    // ...
    imports: [
        BrowserModule,
        AppRoutingModule,
        FormsModule,
        ReactiveFormsModule,
        HttpClientModule,
        CoreModule
    ],
    providers: [
        { provide: LOCALE_ID, useValue: 'fr-FR' },
    ],
  // ...
})
export class AppModule {
  constructor() {
    registerLocaleData(fr.default);
  }
}

D'ailleurs, pendant qu'on y est, la gestion du locale de l'application peut aussi se déplacer dans CoreModule et se supprimer de AppModule :

import { LOCALE_ID, NgModule } from '@angular/core';
import { CommonModule, registerLocaleData } from '@angular/common';
import { httpInterceptorProviders } from './interceptors';
import * as fr from '@angular/common/locales/fr';

@NgModule({
  declarations: [],
  imports: [
    CommonModule
  ],
  providers: [
    { provide: LOCALE_ID, useValue: 'fr-FR' },
    httpInterceptorProviders
  ]
})
export class CoreModule {
  constructor() {
    registerLocaleData(fr.default);
  }
}

Il ne reste plus que HeaderComponent à déplacer.

Créez un dossier  components  dans le dossier  core  , et déplacez-y le dossier  header  .

Le dossier core
Le dossier core

Supprimez toute mention de HeaderComponent – sa déclaration et son import – dans AppModule. Votre application ne compilera plus, mais ce n'est que temporaire !

Vous allez maintenant déclarer HeaderComponent dans CoreModule. Ajoutez-le au tableau declarations :

import { LOCALE_ID, NgModule } from '@angular/core';
import { CommonModule, registerLocaleData } from '@angular/common';
import { httpInterceptorProviders } from './interceptors';
import * as fr from '@angular/common/locales/fr';
import { HeaderComponent } from './components/header/header.component';

@NgModule({
  declarations: [
    HeaderComponent
  ],
  imports: [
    CommonModule,
  ],
  providers: [
    { provide: LOCALE_ID, useValue: 'fr-FR' },
    httpInterceptorProviders
  ]
})
export class CoreModule {
  constructor() {
    registerLocaleData(fr.default);
  }
}

Le CLI vous retourne deux erreurs intéressantes, mais qui peuvent être difficiles à comprendre si on ne s'y attend pas.

La première :

error NG8001: 'app-header' is not a known element:

Pourtant on vient bien de déclarer HeaderComponent ?   🤨

Oui, mais on ne l'a pas exporté ! On utilise  <app-header>  dans AppComponent, et AppComponent dépend de AppModule. Il faut donc que AppModule ait "accès" au HeaderModule déclaré par CoreModule. Il faut donc que CoreModule l'exporte :

// ...
@NgModule({
  declarations: [
    HeaderComponent
  ],
  imports: [
    CommonModule,
  ],
  exports: [
    HeaderComponent
  ],
  providers: [
    { provide: LOCALE_ID, useValue: 'fr-FR' },
    httpInterceptorProviders
  ]
})
// ...

Ça règle la première erreur. Mais quid de la deuxième ?

error NG8002: Can't bind to 'routerLinkActiveOptions' since it isn't a known property of 'a'.

On rencontre cette erreur car tout ce qui est  routerLink  ,  routerLinkActive  , etc., est mis à disposition par RouterModule, et CoreModule ne l'importe pas. Réglons ça tout de suite :

// ...
import { RouterModule } from '@angular/router';

@NgModule({
  // ...
  imports: [
    CommonModule,
    RouterModule
  ],
  // ...

Avec ce dernier ajout, l'application compile correctement : CoreModule est terminé !

Passons maintenant aux feature modules !

FaceSnapModule

La gestion des FaceSnaps peut être considérée comme le feature principal de votre application. Il serait donc logique de lui créer son propre feature module.

Comme pour CoreModule, utilisez le CLI :

ng g m face-snaps

Dans le nouveau dossier  face-snaps  , créez un dossier  components  et déplacez-y les quatre components liés aux FaceSnaps :

Le dossier face-snaps
Le dossier face-snaps

Comme avec HeaderComponent dans CoreModule, ajoutez ces quatre components aux  declarations  et aux  exports  de FaceSnapsModule, retirez-les de AppModule, et ajoutez FaceSnapsModule aux  imports  de AppModule.

Eh oui ! Il manque ReactiveFormsModule et RouterModule ! Ajoutez-les aux imports de FaceSnapsModule, et tout fonctionnera correctement à nouveau :

// ...
import { ReactiveFormsModule } from '@angular/forms';
import { RouterModule } from '@angular/router';

@NgModule({
  // ...
  imports: [
    CommonModule,
    ReactiveFormsModule,
    RouterModule
  ],
  // ...

Et FaceSnapsModule est terminé !

LandingPageModule

Créez un dernier module, très simple cette fois-ci, pour la landing page :

ng g m landing-page

Vu que le dossier  landing-page  existe déjà, Angular y insère le module.

De nouveau, créez un dossier  components  , cette fois-ci avec un sous-dossier  landing-page  , et déplacez-y les fichiers du component :

Le dossier landing-page
Le dossier landing-page

À vous de jouer !

Essayez d'implémenter LandingPageModule, avec les imports nécessaires, et enlevez ce qui peut être enlevé de AppModule. 

Voici à quoi devrait ressembler LandingPageModule :

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { LandingPageComponent } from './components/landing-page/landing-page.component';
import { FormsModule } from '@angular/forms';

@NgModule({
  declarations: [
    LandingPageComponent
  ],
  imports: [
    CommonModule,
    FormsModule
  ],
  exports: [
    LandingPageComponent
  ]
})
export class LandingPageModule { }

Il a bien besoin de FormsModule pour le formulaire de newsletter !

Pour AppModule, non seulement vous pouvez enlever LandingPageComponent (en important LandingPageModule), mais vous pouvez également enlever les imports de FormsModule et ReactiveFormsModule, car les modules qui en ont besoin les importent eux-mêmes !

Faites un pas de plus et déplacez l'import de HttpClientModule dans CoreModule. Il n'y a pas besoin de l'exporter : l'import seul suffit dans ce cas précis.

Avec cette dernière étape, votre AppModule donne :

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { AppRoutingModule } from './app-routing.module';
import { CoreModule } from './core/core.module';
import { FaceSnapsModule } from './face-snaps/face-snaps.module';
import { LandingPageModule } from './landing-page/landing-page.module';

@NgModule({
  declarations: [
    AppComponent,
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    CoreModule,
    FaceSnapsModule,
    LandingPageModule
  ],
  bootstrap: [AppComponent]
})
export class AppModule {}

Et votre dossier de travail :

Le dossier app
Le dossier app

C'est quand même plus agréable !

En résumé

  • Il y a trois types principaux de modules :

    • feature modules – regroupent les éléments d'un feature de l'application ;

    • core modules – regroupent les éléments qui sont importés une seule fois dans l'application ;

    • shared modules – regroupent les éléments qui sont importés à plusieurs endroits de l'application.

  • Un module doit importer tout ce dont il a besoin pour générer ses enfants. Par exemple, si un component qu'il déclare contient un formulaire réactif, il devra importer ReactiveFormsModule ;

  • Si un component déclaré par un module enfant est utilisé dans un module parent, le module enfant doit exporter ce component.

Maintenant que votre application a une architecture modulaire, dans le prochain chapitre vous allez apprendre à en profiter pour en améliorer les performances, avec le lazy loading !

Example of certificate of achievement
Example of certificate of achievement