Lazy loading avec Angular 8

14/11/19 dannyEnglish Version

Qu'allons nous faire

Nous allons implémenter le Lazy loading dans notre Application Web.
Nous utiliserons le framework javascript Angular version 8.2.14

Il s'agit de l'étape 3 de notre guide Angular qui nous permettra d'obtenir une Application Web de type PWA.
Nous allons utiliser un projet existant dont les caractéristiques sont

  • Genéré avec Angular CLI
  • Routing

Tous les sources créés sont indiqués en fin de tutoriel.

L' application est à l'adresse suivante 


Avant de commencer

La vitesse à laquelle s'affiche un site web est l'un des critères les plus essentiels pour l'utilisateur.
Et cette vitesse s'apprécie en secondes.
Au delà de 3 secondes 57% des utilisateurs quittent purement et simplement le site.

Quelles méthodes ou techniques doit on alors utiliser pour que notre site web se charge rapidement ?

L'une des techniques est le lazy loading (“chargement fainéant ou paresseux” en français).
Il a pour effet d'accélerer le fonctionnement d'un site web.
Il permet de spécifier quelles parties d'un site web doivent être chargées lors du démarrage.
 


Théorie

Avant d'aller plus loin il nous faut comprendre comment fonctionne Angular.
La commande qui nous intéresse concerne la compilation de notre projet.

Dans notre fichier package.json il s'agit de la commande

  • ng build

Sans rentrer dans les détails cette commande utilise Webpack (un module bundler).
Grâce à Webpack angular utilise les fichiers de notre projet , les compile pour générer dans le répertoire dist un certain nombre de fichiers que nous pourrons déployer sur un serveur web.

Le projet qui nous sert de base dipose de 4 pages Web

  • Home
  • About
  • Contact
  • notfound


La compilation de notre code source génère notamment deux fichiers main-es5.js et main-es2015.js qui contiennent le code de ces 4 pages.
Pour vérifier cette théorie il suffit d'ouvrir le fichier dist/angular-starter/main-es5.js faire une recherche sur le code utilisé dans chacune des 4 pages

  • home works! (code utilisé dans home.component.html)
  • not-found works! (code utilisé dans not-found.component.html)
  • contact works! (code utilisé dans contact.component.html)
  • about works! (code utilisé dans about.component.html)

Ces fichiers et d'autres seront appelés lors de l'affichage du site Web.
Plus de nombre de pages sera grand, plus le fichier sera volumineux et plus l'affichage sera lent.

Le principe du lazy loading va consister à scinder ce fichier en plusieurs parties qui ne seront chargées qu'en temps voulu.

Passons donc à la pratique.


Pratique

Le lazy loading fonctionne en utilisant la notion de modules et non plus celle des composants.

Nous utiliserons la documentation Angular pour appliquer cette technique.
https://angular.io/guide/lazy-loading-ngmodules

Nous allons adapter notre architecture, en créant un module pour chaque élément à afficher.
Home et not-found resteront gérés de façon classique sous forme de composants.

Utilisons la commande ng generate module que nous offre angular-cli.

# Création des modules
ng generate module modules/general/contact --routing  --module=app
ng generate module modules/general/about --routing  --module=app

# Création des modules (méthode 2)
ng g m modules/general/contact --routing  --module=app
ng g m modules/general/about --routing  --module=app

Les fichiers nécéssaires à chaque composant sont créés automatiquement.

Par exemple pour le composant Home

  • home-routing.module.ts
  • home.module.ts


Le fichier app.module.ts est automatiquement modifié comme suit.

src/app/app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppComponent } from './app.component';
import { HomeComponent } from './modules/general/home/home.component';
import { ContactComponent } from './modules/general/contact/contact.component';
import { AboutComponent } from './modules/general/about/about.component';
import { NotFoundComponent } from './modules/general/not-found/not-found.component';
import { AppRoutingModule } from './app-routing.module';
import { ContactModule } from './modules/general/contact/contact.module';
import { AboutModule } from './modules/general/about/about.module';

@NgModule({
  declarations: [
    AppComponent,
    HomeComponent,
    ContactComponent,
    AboutComponent,
    NotFoundComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    ContactModule,
    AboutModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

Nous devons procéder à certaines modifications sur les fichiers suivants.

  • app-routing.module.ts
  • app.module.ts
  • about-routing.module.ts
  • about.module.ts
  • contact-routing.module.ts
  • contact.module.ts

Le lazy loading sera appliqué sur Contact et About
Au niveau de AppRoutingModule, nous devons mettre à jour les routes en utilisant loadchildren.

L'erreur suivante n'apparait pas si vous utilisez angular version 8.2.14 dans ce cas optez pour la méthode 2.

Angular Version 8 préconise une nouvelle façon d'utiliser loadchildren.
Dans ce cas loadchildren est suivi d'une function  import('...').
Cette façon de traiter loadchildren ne semble pas fonctionner pour l'instant avec le Server Side Rendering.
Nous ne l'utiliserons donc pas.

Mais nous vous présentons les 2 méthodes

  • méthode 1 version angular 7 (version à utiliser avec le server side rendering)
  • méthode 2 version angular 8
src/app/app-routing.module.ts (méthode 1)
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

import { HomeComponent } from './modules/general/home/home.component';
import { NotFoundComponent } from './modules/general/not-found/not-found.component';

const routes: Routes = [
  { path: '', component: HomeComponent, },
  {
    path: 'about',
    loadChildren: './modules/general/about/about.module#AboutModule' ,
  },
  {
    path: 'contact',
    loadChildren: './modules/general/contact/contact.module#ContactModule' ,
  },
  { path: '**', component: NotFoundComponent }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule],
  declarations: []
})
export class AppRoutingModule { }
src/app/app-routing.module.ts (méthode 2)
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

import { HomeComponent } from './modules/general/home/home.component';
import { NotFoundComponent } from './modules/general/not-found/not-found.component';

const routes: Routes = [
  { path: '', component: HomeComponent, },
  {
    path: 'about',
    loadChildren: () => import('./modules/general/about/about.module').then(mod => mod.AboutModule)
  },
  {
    path: 'contact',
    loadChildren: () => import('./modules/general/contact/contact.module').then(mod => mod.ContactModule)
  },
  { path: '**', component: NotFoundComponent }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule],
  declarations: []
})
export class AppRoutingModule { }

Il nous faut modifier le module AppModule et ne laisser que les composants HomeComponent et NotFoundComponent

src/app/app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppComponent } from './app.component';
import { HomeComponent } from './modules/general/home/home.component';
import { NotFoundComponent } from './modules/general/not-found/not-found.component';
import { AppRoutingModule } from './app-routing.module';

@NgModule({
  declarations: [
    AppComponent,
    HomeComponent,
    NotFoundComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

Il ne reste plus qu'à modifier les fichiers de routing et module pour Contact et About.

src/app/modules/general/about/about-routing.module.ts
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

import { AboutComponent } from './about.component';

const routes: Routes = [
  { path: '', component: AboutComponent },
];

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

src/app/modules/general/about/about.module.ts
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';

import { AboutComponent } from './about.component';
import { AboutRoutingModule } from './about-routing.module';

@NgModule({
  imports: [
    CommonModule,
    AboutRoutingModule
  ],
  exports: [
    AboutComponent
  ],
  declarations: [
    AboutComponent
  ],
  providers: [
  ],
})
export class AboutModule { }
src/app/modules/general/contact/contact-routing.module.ts
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

import { ContactComponent } from './contact.component';

const routes: Routes = [
  { path: '', component: ContactComponent },
];

@NgModule({
  imports: [RouterModule.forChild(routes)],
  exports: [RouterModule]
})
export class ContactRoutingModule { }
src/app/modules/general/contact/contact.module.ts
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';

import { ContactComponent } from './contact.component';
import { ContactRoutingModule } from './contact-routing.module';

@NgModule({
  imports: [
    CommonModule,
    ContactRoutingModule
  ],
  exports: [
    ContactComponent
  ],
  declarations: [
    ContactComponent
  ],
  providers: [
  ],
})
export class ContactModule { }

Vérification

Pour vérifier la théorie du lazy loading nous devons effectuer une nouvelle compilation (npm run build)

Dans le répertoire dist/angular-starter nous obtenons cette fois 3 fichiers

  • main.js
  • modules-about-about-module.js
  • modules-contact-contact-module.js

Le code de chacune de nos 4 pages est maintenant disposé de la façon suivante

  • home works! (code trouvé dans main.js)
  • not-found works! (code trouvé dans main.js)
  • contact works! (code trouvé dans modules-contact-contact-module.js)
  • about works! (code utilisé modules-about-about-module.js)

Si nous éxécutons l'application (npm run start) nous pouvons voir dans Chrome (F12) au niveau de l'onglet Network comment les fichiers sont chargés.

  • Au lancement du site : main.js est appelé.
  • A la sélection de About : modules-about-about-module.js est appelé une seule fois
  • A la sélection de Contact : modules-contact-contact-module.js est appelé une seule fois


Si nous lancons l'url localhost/contact

  • Dans ce cas main.js et seulement modules-contact-contact-module.js sont appelés


Conclusion : 
Quelle que soit le nombre de pages, le fichier main.js aura toujours la même taille.
Le lancement du site qui charge le fichier main.js se fera toujours à la même vitesse. 


Tests

Il ne reste plus qu' à tester l'application.

# Développement
npm run start
http://localhost:4200/

# Tests
npm run lint
npm run test
npm run e2e

# Production
npm run build

Laissez un commentaire

Votre avis
Cette adresse ne sera pas publiée