• 8 heures
  • Moyenne

Ce cours est visible gratuitement en ligne.

course.header.alt.is_video

course.header.alt.is_certifying

J'ai tout compris !

Mis à jour le 31/03/2022

Boostez la performance avec le lazy loading

L'application Snapface, dans son état actuel, est générée sous forme d'un seul fichier JavaScript pour sa logique principale.

La conséquence de cette architecture est que tout utilisateur qui s'en sert doit charger l'application entière, même s'il ne se sert que d'une partie. Ça entraîne des temps de chargement inutilement longs, et l'expérience utilisateur en souffre.

Angular a une solution à ce problème, appelée lazy loading. Il s'agit d'une technique de routing – qui dépend de l'architecture de modules que vous avez implémentée dans le chapitre précédent – qui fera en sorte qu'Angular, au moment du déploiement, crée des fichiers JS séparés pour chaque feature module. Ces fichiers sont ensuite chargés au besoin – quand l'utilisateur accède à la route qui y correspond.

Mettons en place dès maintenant le lazy loading dans l'application Snapface !

Modifiez le routing

Le module pour lequel vous allez implémenter le lazy loading est FaceSnapsModule.

Pour qu'un module puisse être lazy loaded, il doit s'occuper de son propre routing, mais pour l'instant, tout le routing de l'application se trouve dans AppRoutingModule. Du coup, vous allez déménager le routing de FaceSnapsModule dans un nouveau fichier,  face-snaps-routing.module.ts  , à côté de  face-snaps.module.ts  :

import { NgModule } from '@angular/core';
import { Routes } from '@angular/router';

const routes: Routes = [];

@NgModule({
  imports: [],
  exports: []
})
export class FaceSnapsRoutingModule {}

Avant d'y copier les routes, réfléchissons à la structure du routing.

À part la route  create  , toutes les routes liées aux FaceSnaps commencent par  facesnaps/  . Du coup, si on transforme la route  create  pour aussi commencer par  facesnaps/  , on pourrait dire que AppRoutingModule délègue toutes les routes commençant par  facesnaps/  à FaceSnapsModule !

Ça veut dire que les routes que vous allez enregistrer dans FaceSnapsRoutingModule ne doivent pas contenir le préfixe :

const routes: Routes = [
  { path: ':id', component: SingleFaceSnapComponent },
  { path: '', component: FaceSnapListComponent },
  { path: 'create', component: NewFaceSnapComponent },
];

La technique pour enregistrer ces routes ressemble à celle de AppRoutingModule, avec une nuance importante :

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { SingleFaceSnapComponent } from './components/single-face-snap/single-face-snap.component';
import { FaceSnapListComponent } from './components/face-snap-list/face-snap-list.component';
import { NewFaceSnapComponent } from './components/new-face-snap/new-face-snap.component';

const routes: Routes = [
  { path: ':id', component: SingleFaceSnapComponent },
  { path: '', component: FaceSnapListComponent },
  { path: 'create', component: NewFaceSnapComponent },
];

@NgModule({
  imports: [RouterModule.forChild(routes)],
  exports: [RouterModule]
})
export class FaceSnapsRoutingModule {}

Au lieu d'utiliser RouterModule.forRoot() (qui ne doit être appelée qu'une seule fois par votre routeur racine), vous utilisez RouterModule.forChild() pour enregistrer ces routes au routeur déjà créé.

Pour que ces routes soient enregistrées, il faut importer FaceSnapsRoutingModule dans FaceSnapsModule :

imports: [
    CommonModule,
    ReactiveFormsModule,
    FaceSnapsRoutingModule
],

Vous pouvez retirer l'import de RouterModule, car votre module de routing vous le fournit !

Il faut dire à AppRoutingModule de déléguer les routes  facesnaps/  à FaceSnapsModule. Vous allez utiliser une syntaxe spécifique qui permet à Angular de mettre en place le lazy loading :

const routes: Routes = [
  { path: 'facesnaps', loadChildren: () => import('./face-snaps/face-snaps.module').then(m => m.FaceSnapsModule) },
  { path: '', component: LandingPageComponent }
];

Cette syntaxe fait en sorte qu'Angular génère un fichier JS séparé pour FaceSnapsModule, et l'application ne la charge que si l'utilisateur visite une route  facesnaps/  .

Pour terminer l'implémentation, il faut retirer l'import de FaceSnapsModule dans AppModule :

imports: [
    BrowserModule,
    AppRoutingModule,
    CoreModule,
    LandingPageModule
],

Ça y est !

Vous me croyez sur parole, non ?

Bon OK, si vous voulez quand même vérifier, commencez par charger l'application à http://localhost:4200. Allez sur l'onglet Network (Réseau) des DevTools de votre navigateur, et filtrez si possible pour ne voir que les fichiers JS chargés.

Le premier chargement donne :

Premier chargement
Premier chargement

Précédemment, toute la logique se trouvait dans  main.…  . Cependant, avec le lazy loading implémenté, si vous cliquez maintenant sur le lien FaceSnaps ou le bouton Continuer vers Snapface...

Un nouveau fichier apparaît !
Un nouveau fichier apparaît !

Vous voyez le chargement du fichier JS qui correspond à FaceSnapsModule !

Déboguez le routing

Vous vous dites peut-être que réparer ce bug sera simple. Si vous avez une idée, je vous invite à l'essayer.

Il faut, en effet, modifier la méthode  onAddNewFaceSnap()  de HeaderComponent :

onAddNewFaceSnap() {
    this.router.navigateByUrl('/facesnaps/create');
}

C'est bien ce qu'il faut, mais si vous cliquez sur le bouton + du Header, vous verrez une erreur dans la console :

Erreur 404
Erreur 404

Est-ce que vous comprenez ce qui ne va pas ? 🤓

En fait, le routing essaie de traiter  "create"  comme un  id  de FaceSnap !

Pourquoi ? Eh bien parce que la route  :id  est avant la route  create  dans le tableau de Routes. Du coup, le routeur voit une route de la forme  facesnaps/quelque-chose  et présume que  "create"  correspond à un  id  !

Ce bug illustre très bien l'importance de l'ordre des routes lors de leur enregistrement. Angular traverse le tableau de Routes dans l'ordre, et applique la première qui ressemble à la route demandée.

Du coup, si vous changez l'ordre :

const routes: Routes = [
  { path: 'create', component: NewFaceSnapComponent },
  { path: ':id', component: SingleFaceSnapComponent },
  { path: '', component: FaceSnapListComponent },
];

Tout fonctionne de nouveau correctement, et votre module FaceSnapsModule est bien lazy loaded !

En résumé

  • Le lazy loading génère un fichier JS séparé, pour un module qui n'est chargé que si l'utilisateur visite la route correspondante ;

  • Pour implémenter le lazy loading, le module en question doit s'occuper de tout son routing ;

  • Le routing est ensuite délégué par le routeur principal avec une syntaxe particulière : 

    { path: 'module-route', loadChildren: () => import('path/to/module').then(m => m.NameOfModule) }

Dans le prochain et dernier chapitre, vous découvrirez une autre technique liée au routing qui permet de protéger certaines routes de votre application, avec les guards.

Exemple de certificat de réussite
Exemple de certificat de réussite