Tutoriels Backend
Lazy loading avec Angular 18
Nous allons implémenter le lazy loading dans notre Application Web.
Nous utiliserons pour cela le framework javascript Angular version 18.0.2
Angular nous permettra de charger les modules à la demande.
Si vous n'avez pas le temps de lire ce guide en entier,
téléchargez le maintenant
Qu'allons nous faire
Nous allons configurer le Lazy loading dans notre Application Web avec Angular version 18.0.2
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élérer 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 6 pages Web
- Home
- About
- Contact
- Login
- Signup
- notfound
La compilation de notre code source génère notamment un fichier main.js qui contient le code de ces 6 pages (ou un fichier du type main.xxxxxxx.js)
Pour vérifier cette théorie il suffit d'ouvrir le fichier dist/angular-starter/browser/main.js faire une recherche sur le code utilisé dans chacune des 6 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)
- login works! (code utilisé dans login.component.html)
- signup works! (code utilisé dans signup.component.html)
- about works! (code utilisé dans about.component.html)
Ce fichier et d'autres seront appelés lors de l'affichage du site Web.
Plus le 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.
Création du projet
Plutôt que de tout recréer nous allons utiliser un projet qui contient le routing.
Je vous donne les commandes à lancer , Git est évidemment obligatoire pour récupérer le code source.
# Créez un répertoire demo (le nom est ici arbitraire)
mkdir demo
# Allez dans ce répertoire
cd demo
# Récupérez le code source sur votre poste de travail
git clone https://github.com/ganatan/angular-react-routing
# Allez dans le répertoire qui a été créé
cd angular-react-routing
cd angular
# Exécutez l'installation des dépendances (ou librairies)
npm install
# Exécutez le programme
npm run start
# Vérifiez son fonctionnement en lançant dans votre navigateur la commande
http://localhost:4200/
Pratique
Le lazy loading fonctionne en utilisant la notion de modules ou celle de composant (notamment les composants standalone).
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 , not-found et about 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 (méthode 1)
ng generate module pages/general/contact --routing
ng generate module pages/general/login --routing
ng generate module pages/general/signup --routing
# Création des modules (méthode 2)
ng g m pages/general/contact --routing
ng g m pages/general/login --routing
ng g m pages/general/signup --routing
Les fichiers nécessaires à chaque composant sont créés automatiquement.
Par exemple pour le composant Contact
- contact-routing.module.ts
- contact.module.ts
Le lazy loading sera appliqué sur Contact, Login et Signup
Au niveau de app-route.ts, nous devons mettre à jour les routes en utilisant loadchildren et loadcomponent.
import { Routes } from '@angular/router';
import { HomeComponent } from './pages/general/home/home.component';
import { NotFoundComponent } from './pages/general/not-found/not-found.component';
export const routes: Routes = [
{ path: '', component: HomeComponent, },
{
path: 'login',
loadChildren: () => import('./pages/general/login/login.module')
.then(mod => mod.LoginModule)
},
{
path: 'signup',
loadChildren: () => import('./pages/general/signup/signup.module')
.then(mod => mod.SignupModule)
},
{
path: 'contact',
loadChildren: () => import('./pages/general/contact/contact.module')
.then(mod => mod.ContactModule)
},
{
path: 'about',
loadComponent: () => import('./pages/general/about/about.component')
.then(mod => mod.AboutComponent)
},
{ path: '**', component: NotFoundComponent }
];
Il ne reste à modifier les fichiers de routing et module pour Contact, Login, et Signup.
import { Component } from '@angular/core';
@Component({
selector: 'app-contact',
templateUrl: './contact.component.html',
styleUrl: './contact.component.css'
})
export class ContactComponent {
}
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 { }
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 { }
import { Component } from '@angular/core';
@Component({
selector: 'app-signup',
templateUrl: './signup.component.html',
styleUrl: './signup.component.css'
})
export class SignupComponent {
}
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { SignupComponent } from './signup.component';
const routes: Routes = [
{ path: '', component: SignupComponent },
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class SignupRoutingModule { }
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { SignupComponent } from './signup.component';
import { SignupRoutingModule } from './signup-routing.module';
@NgModule({
imports: [
CommonModule,
SignupRoutingModule
],
exports: [
SignupComponent
],
declarations: [
SignupComponent
],
providers: [
],
})
export class SignupModule { }
import { Component } from '@angular/core';
@Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrl: './login.component.css'
})
export class LoginComponent {
}
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { LoginComponent } from './login.component';
const routes: Routes = [
{ path: '', component: LoginComponent },
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class LoginRoutingModule { }
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { LoginComponent } from './login.component';
import { LoginRoutingModule } from './login-routing.module';
@NgModule({
imports: [
CommonModule,
LoginRoutingModule
],
exports: [
LoginComponent
],
declarations: [
LoginComponent
],
providers: [
],
})
export class LoginModule { }
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 plusieurs fichiers en plus du fichier main.js
- main.js
- chunk-xxx1.js
- chunk-xxx2.js
- chunk-xxx3.js
- chunk-xxx4.js
Remarque:
Les noms peuvent être différents notamment avec des numéros c'est webpack qui gère le nommage.
Le code de chacune de nos pages est maintenant disposé de la façon suivante
- home works! (code trouvé dans main.js)
- not-found works! (code trouvé dans main.js)
- about works! (code utilisé main.js)
- contact works! (code trouvé dans chunk-xxx1.js)
- login works! (code trouvé dans chunk-xxx2.js)
- signup works! (code trouvé dans chunk-xxx3.js)
Si nous exé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 login: chunk-xxx2.js est appelé une seule fois
- A la sélection de signup: chunk-xxx3.js est appelé une seule fois
- A la sélection de Contact : chunk-xxx4.js est appelé une seule fois
Si nous lançons l'url localhost/contact
- Dans ce cas main.js et seulement chunk-xxx1.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.
Child Routes
Cette application contient aussi la gestion des Child routes.
Les différents mots clé qui pourraient y faire référence sont les suivants
- routing
- sous routing
- sub routing
- nested routes
- children routes
Cette question est évoquée dans la documentation
https://angular.io/guide/router#child-route-configuration
Vous retrouverez dans le dépôt sur github l'ajout de la notion de routing avec Children.
Dans le tutoriel sur le routing trois composants ont été rajoutés dans Contact
- mailing
- mapping
- website
Il nous faut rajouter la notion de modules sur ces composants.
5 fichiers doivent notamment être modifiés pour en prendre compte.
- contact-routing.module.ts
- mailing-routing.module.ts
- mapping-routing.module.ts
- website-routing.module.ts
- app.routes.ts
Et 2 fichiers doivent être créés.
- about-config.ts
- about-routes.ts
# Rajout du module mailing
ng generate module pages/general/contact/mailing --routing
# Rajout du module mapping
ng generate module pages/general/contact/mapping --routing
# Rajout du module website
ng generate module pages/general/contact/website --routing
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { ContactComponent } from './contact.component';
const routes: Routes = [
{
path: '', component: ContactComponent, children: [
{
path: '',
loadChildren: () => import(`./mailing/mailing.module`)
.then(mod => mod.MailingModule)
},
{
path: 'mailing',
loadChildren: () => import(`./mailing/mailing.module`)
.then(mod => mod.MailingModule)
},
{
path: 'mapping',
loadChildren: () => import(`./mapping/mapping.module`)
.then(mod => mod.MappingModule)
},
{
path: 'website',
loadChildren: () => import(`./website/website.module`)
.then(mod => mod.WebsiteModule)
},
{
path: '**',
loadChildren: () => import(`./mailing/mailing.module`)
.then(mod => mod.MailingModule)
},
]
},
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class ContactRoutingModule { }
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { MailingComponent } from './mailing.component';
const routes: Routes = [
{ path: '', component: MailingComponent, children: [] }
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class MailingRoutingModule { }
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { MappingComponent } from './mapping.component';
const routes: Routes = [
{ path: '', component: MappingComponent, children: [] }
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class MappingRoutingModule { }
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { WebsiteComponent } from './website.component';
const routes: Routes = [
{ path: '', component: WebsiteComponent, children: [] }
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class WebsiteRoutingModule { }
import { Routes } from '@angular/router';
import { HomeComponent } from './pages/general/home/home.component';
import { NotFoundComponent } from './pages/general/not-found/not-found.component';
export const routes: Routes = [
{ path: '', component: HomeComponent, },
{
path: 'login',
loadChildren: () => import('./pages/general/login/login.module')
.then(mod => mod.LoginModule)
},
{
path: 'signup',
loadChildren: () => import('./pages/general/signup/signup.module')
.then(mod => mod.SignupModule)
},
{
path: 'contact',
loadChildren: () => import('./pages/general/contact/contact.module')
.then(mod => mod.ContactModule)
},
{
path: 'about',
loadChildren: () => import('./pages/general/about/about.routes').then(routes => routes.routes)
},
{ path: '**', component: NotFoundComponent }
];
import { Routes } from '@angular/router';
import { AboutComponent } from './about.component';
export const routes: Routes = [
{
path: '', component: AboutComponent, children: [
{
path: '',
loadComponent: () => import(`./experience/experience.component`)
.then(mod => mod.ExperienceComponent)
},
{
path: 'experience',
loadComponent: () => import(`./experience/experience.component`)
.then(mod => mod.ExperienceComponent)
},
{
path: 'skill',
loadComponent: () => import(`./skill/skill.component`)
.then(mod => mod.SkillComponent)
},
{
path: '**',
loadComponent: () => import(`./experience/experience.component`)
.then(mod => mod.ExperienceComponent)
},
]
},
];
import { ApplicationConfig } from '@angular/core';
import { provideRouter } from '@angular/router';
import { routes } from './about.routes';
export const aboutConfig: ApplicationConfig = {
providers: [provideRouter(routes)]
};
Tests
Avant d'effectuer il nous faut adapter les fichiers de tests correspondant.
- contact.component.spec.ts
- signup.component.spec.ts
- login.component.spec.ts
Il ne restera alors plus qu' à tester l'application.
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { SignupComponent } from './signup.component';
describe('SignupComponent', () => {
let component: SignupComponent;
let fixture: ComponentFixture<SignupComponent>;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [SignupComponent]
});
fixture = TestBed.createComponent(SignupComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { ContactComponent } from './contact.component';
describe('ContactComponent', () => {
let component: ContactComponent;
let fixture: ComponentFixture<ContactComponent>;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
RouterTestingModule
],
declarations: [ContactComponent]
});
fixture = TestBed.createComponent(ContactComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { LoginComponent } from './login.component';
describe('LoginComponent', () => {
let component: LoginComponent;
let fixture: ComponentFixture<LoginComponent>;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [LoginComponent]
});
fixture = TestBed.createComponent(LoginComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
# Développement
npm run start
http://localhost:4200/
# Tests
npm run lint
npm run test
# Production
npm run build
Code source
Le code source utilisé en début de tutoriel est disponible sur github
https://github.com/ganatan/angular-react-routing
Le code source obtenu à la fin de ce tutoriel est disponible sur github
https://github.com/ganatan/angular-react-lazy-loading
Les étapes suivantes vous permettront d'obtenir une application prototype.
- Etape 4 : Bootstrap avec Angular
- Etape 5 : Modules avec Angular
- Etape 6 : Server Side Rendering avec angular
- Etape 7 : Progressive Web App avec Angular
- Etape 8 : Search Engine Optimization avec Angular
- Etape 9 : HttpClient avec Angular
Les étapes suivantes vous permettront d'améliorer ce prototype
- Components avec Angular
- Services avec Angular
- Template Driven Forms avec Angular
- Charts avec Angular
Cette dernière étape permet d'obtenir un exemple d'application
Le code source de cette application finale est disponible sur GitHub
https://github.com/ganatan/angular-app