WEBZINE

Optimiser le chargement de son application Angular

Lorsque l’on construit une application complexe on se retrouve très vite avec des dizaines de composants, de services, de librairies, … qui vont fortement impacter le temps de chargement des scripts de notre application, sans considérer le chargement des données elles-mêmes.

En effet, par défaut Angular charge tous les scripts de l’application à la première connexion. Une fois les scripts chargés il reste ensuite à l’explorateur internet à les interpréter et afficher le résultat. Selon la taille de l’application on peut très vite arriver à plusieurs secondes de chargement avant d’avoir le premier écran.

Il existe pourtant plusieurs techniques et stratégies pour optimiser ce temps de chargement et permettre à l’utilisateur d’interagir avec le site beaucoup plus vite.

Chargement à la demande ou lazy-loading

Lorsque l’on crée une application, il n’est pas optimal de charger toutes les données de celle-ci dès le début pour ensuite ne plus interagir avec le serveur. C’est le même principe pour le chargement de l’application elle-même : charger tous les scripts de l’application alors que potentiellement on ne veut accéder qu’à 10% des pages n’est pas la meilleure solution.

Par défaut, le comportement d’une application Angular est de charger tous les composants à la première connexion. Pour changer ce mécanisme et passer en mode « lazy-loading » c’est assez simple.

Il suffit de gérer chaque « feature » de l’application dans un nouveau module. Pour générer ce module le plus simple est d’utiliser le CLI ng generate module mon-module –routes mon-module –module app.module (je vous conseille fortement d’utiliser le CLI d’Angular le plus possible)

Tout au long de cet article, nous allons utiliser une démonstration pour illustrer les modifications de configuration.

Nous avons une application dans laquelle nous déclarons un module parent-un.module.ts . Pour activer le « lazy-loading » sur ce module voici comment procéder.

App-routing.module.ts

import {CustomPreloadingStrategy} from "./CustomPreloadingStrategy";
const routes: Routes = [
{path: 'parentUn', loadChildren: () => import('./parent-un/parent-un.module').then(m => m.ParentUnModule)}
@NgModule({
imports: [RouterModule.forRoot(routes, {preloadingStrategy: CustomPreloadingStrategy})],
exports: [RouterModule]
})
export class AppRoutingModule {
}

 

C’est la déclaration de la route qui va faire le nécessaire. Là où une page déclarée de façon classique se fait via le composant, la déclaration en « lazy-loading » se sert du module.

Le module parent-un prend lui la forme suivante :

parent-un-routing.module.ts

import {CustomPreloadingStrategy} from "./CustomPreloadingStrategy";
const routes: Routes = [
  {path: 'parentUn', loadChildren: () => import('./parent-un/parent-un.module').then(m => m.ParentUnModule)}
@NgModule({
  imports: [RouterModule.forRoot(routes, {preloadingStrategy: CustomPreloadingStrategy})],
  exports: [RouterModule]
})
export class AppRoutingModule {
}

C’est la déclaration de la route qui va faire le nécessaire. Là où une page déclarée de façon classique se fait via le composant, la déclaration en « lazy-loading » se sert du module.

Le module parent-un prend lui la forme suivante :

parent-un-routing.module.ts

const routes: Routes = [{
  path: '', component: ParentUnComponent,
  children: [
    {path: 'enfantUn', loadChildren: () => import('./enfant-un/enfant-un.module').then(m => m.EnfantUnModule)},
    {path: 'enfantDeux', loadChildren: () => import('./enfant-deux/enfant-deux.module').then(m => m.EnfantDeuxModule)}
  ]
},
];
@NgModule({
  imports: [RouterModule.forChild(routes)],
  exports: [RouterModule]
})
export class ParentUnRoutingModule {
}

Dans notre module nous avons déclaré à nouveau deux sous-modules en « lazy-loading » enfant-un et enfant-deux.

Le composant ParentUnComponent est quant à lui définit à la racine de la route.

Nous verrons plus bas dans l’article le comportement du navigateur.

Être efficace en utilisant une stratégie de chargement

Nous avons vu comment charger les modules à la demande, mais attendre que l’utilisateur clique sur un lien pour charger le code de celui-ci n’est pas non plus la meilleure des stratégies car ce code peut lui aussi être conséquent.

Une manière de répondre à cette question est de définir une stratégie de chargement implémentant la classe PreloadingStrategie.

Pour notre exemple voici l’implémentation que nous avons :

customPreloadingStrategy.ts

@Injectable()
export class CustomPreloadingStrategy implements PreloadingStrategy {
    preload(route: Route, loadMe: () => Observable): Observable {
    if (route.data && route.data['preload']) {
      let delay:number=route.data['delay'];
      console.log('preload called on '+route.path+' delay is '+delay);
      return timer(delay).pipe(
        flatMap( _ => {
          console.log("Loading now "+ route.path);
          return loadMe() ;
        }));
    } else {
      console.log('no preload for the path '+ route.path);
      return of(null);
    }
  }
}

La stratégie mise en place est de déclencher le chargement du module au bout d’un laps de temps paramétrable. Mais on pourrait très bien imaginer d’autres stratégies, liées à l’UX de l’application pour faciliter l’enchaînement des pages ou bien de coupler tout cela aux droits d’accès de l’utilisateur pour ne charger uniquement les pages auxquelles il a droits.

Pour utiliser cette stratégie de chargement, il suffit de l’ajouter au chargement de la route de la façon suivante.

App-routing.module.ts

import {CustomPreloadingStrategy} from "./CustomPreloadingStrategy";

const routes: Routes = [
  {path: 'parentUn', loadChildren: () => import('./parent-un/parent-un.module').then(m => m.ParentUnModule)},
  {
    path: 'parentDeux',
    data: {preload: true, delay: 3000},
    loadChildren: () => import('./parent-deux/parent-deux.module').then(m => m.ParentDeuxModule)
  }];
@NgModule({
  imports: [RouterModule.forRoot(routes, {preloadingStrategy: CustomPreloadingStrategy})],
  exports: [RouterModule]
})
export class AppRoutingModule {
}

Ici on déclenche le chargement si preload est vrai et au bout du temps déclaré dans delay.

Voyons maintenant comment le navigateur se comporte via une video 

On remarque sur la vidéo que les pages en « lazy-loading » sont chargées au clic de l’utilisateur et que la stratégie de chargement est déclenchée automatiquement.

Une meilleure expérience utilisateur, le server-side rendering

Lorsque l’on arrive sur un site web, c’est désagréable de rester bloqué sur une page blanche pendant plusieurs secondes avant que celle-ci ne s’affiche. C’est particulièrement visible quand on a une mauvaise connexion internet, comme en 3G par exemple.

Avec le « server-side rendering » nous pouvons afficher le contenu d’une page bien plus rapidement afin de faire patienter l’utilisateur le temps que celle-ci devienne interactive.

Pour ajouter le « server-side rendering » à un projet c’est encore une fois assez simple, il suffit d’exécuter la commande ng add @nguniversal/express-engine.

Cette commande va ajouter trois fichiers à l’application : main.server.ts, app.server.module.ts et server.ts.

Une fois le serveur lancé via la commande npm run dev:ssr on accède à l’application de façon normale.

Comparons le chargement d’une même application avec et sans serveur. analysé avec Lighthouse (https://developers.google.com/web/tools/lighthouse/)

Sans serveur

Avec serveur

On peut voir que pour la même application, bien que le premier contenu soit chargé à la même vitesse, le moment où l’application devient interactive intervient 300ms plus tôt avec le serveur. Le score de performance est également plus élevé pour le serveur.

Un autre point positif de l’utilisation d’un serveur, est l’optimisation du SEO pour le référencement web. Parfois, paramétrer un serveur pour son application Angular n’est pas forcément la solution idéale car cela introduit une complexité supplémentaire, un serveur à maintenir et paramétrer.

💡 A retenir

Ce que l’on a pu voir ici c’est qu’il est facile d’améliorer le temps de chargement des scripts de l’application via une technique très simple qui est le « lazy-loading ».

Pour ce qui est du « server-side rendering » c’est très utile pour optimiser le chargement d’un site sur mobile, avec une bande passante assez faible. Mais d’un autre côté cela demande de maintenir et faire évoluer un serveur dans la durée.

Nous nous sommes concentrés ici sur l’optimisation côté code avec le « lazy-loading » et le « server-side rendering ». Mais il existe aussi d’autres axes de travail côté build et serveur de production, comme la compression de l’application par exemple.

🔗 Pour aller plus loin…

Pour mieux visualiser les conséquences de l’optimisation du chargement de son applicaiton Angular, voici des vidéosexplicatives :

📚 Références :

  • https://angular.io/
  • https://dzone.com/articles/angular-server-side-rendering-ssr-made-easy-with-a

 

Faites-nous part de vos impressions sur cet article en nous écrivant ! Il n’est pas figé et évoluera au fil de vos retours. Et, si vous avez apprécié, partagez-le ! 👇

Partager sur facebook
Facebook
Partager sur google
Google+
Partager sur twitter
Twitter
Partager sur linkedin
LinkedIn
Partager sur email
Email

Parce que lire, c’est fondamental !

Conseils, innovation ou success stories: découvrez les articles écrits ou mis en avant par nos Hcubiens. Des nouveautés vous attendent chaque semaine ! Bonne lecture !

Parce que lire, c’est fondamental !

Conseils, innovation ou success stories: découvrez les articles écrits ou mis en avant par nos Hcubiens. Des nouveautés vous attendent chaque semaine ! Bonne lecture !

CONTACTEZ NOUS

contact@hcube-conseil.fr
04 42 39 66 98
Nous trouver

INFORMATIONS

Nous contacter
Ils parlent de nous
Travailler avec nous
Conditions d’utilisation
RGPD

HCube Conseil 2019