Les nouveautés d'Angular 19 en 4 minutes

Angular 19 vient de sortir, et il y a beaucoup de nouveautés intéressantes : hydratation incrémentale, linkedSignal, l'API des ressources, et plein d'autres choses. Venez découvrir tout ça en moins de 4 minutes !

Skip to content

Vous souhaitez recevoir de l'aide sur ce sujet ? rejoignez la communauté Angular.fr sur Discord.

Maîtriser les Preloading Strategies

WARNING

Pour ce suivre cette article, vous devez d'abord connaître la notion de lazy-loading. Si ce n'est pas le cas, lisez notre article sur le lazy-loading.

Imaginez que vous gérez un grand magasin en ligne. Au lieu d'attendre que les clients cliquent sur une catégorie pour charger ses produits, vous pourriez anticiper leurs besoins et commencer à charger certaines catégories populaires en arrière-plan. C'est exactement ce que font les Preloading Strategies dans Angular : elles anticipent les besoins de l'utilisateur en chargeant certains modules à l'avance.

Commençons par un exemple simple. Supposons que nous ayons une application de gestion d'utilisateurs avec plusieurs routes chargées paresseusement (lazy-loaded).

typescript
import { Routes } from '@angular/router';

export const routes: Routes = [
  { 
    path: 'users', 
    loadComponent: () => import('./components/features/user/user.component').then(m => m.UserComponent)
  },
  { 
    path: 'admin', 
    loadComponent: () => import('./components/features/admin/admin.component').then(m => m.AdminComponent)
  }
];
typescript
import { ApplicationConfig } from '@angular/core';
import { provideRouter, withPreloading, PreloadAllModules } from '@angular/router';
import { provideHttpClient } from '@angular/common/http';
import { routes } from './app.routes';

export const appConfig: ApplicationConfig = {
  providers: [
    provideRouter(routes, withPreloading(PreloadAllModules)),
    provideHttpClient()
  ]
};

Le "chunk" est chargé même si l'utilisateur ne navigue pas vers cette route. Exemple complet

Examinons de plus près ce que fait loadComponent et comment la stratégie PreloadAllModules affecte le chargement :

  1. loadComponent : Cette fonction est utilisée pour le chargement paresseux (lazy loading) des composants. Elle permet de charger un composant uniquement lorsque sa route est activée, plutôt que de le charger au démarrage de l'application.

    typescript
    loadComponent: () => import('./components/features/user/user.component').then(m => m.UserComponent)
    • Cette ligne utilise une importation dynamique pour charger le fichier du composant.
    • Le composant n'est chargé que lorsque l'utilisateur navigue vers la route correspondante.
    • Cela permet de réduire le temps de chargement initial de l'application en ne chargeant que les composants nécessaires.
  2. Stratégie PreloadAllModules : Cette stratégie, utilisée avec withPreloading dans app.config.ts, modifie le comportement du chargement paresseux :

    typescript
    provideRouter(routes, withPreloading(PreloadAllModules))
    • Sans préchargement, les composants ne seraient chargés que lorsque l'utilisateur navigue vers leur route.
    • Avec PreloadAllModules, Angular commence à charger tous les composants lazy-loaded en arrière-plan dès que l'application initiale est chargée.
    • Cela signifie que même si UserComponent et AdminComponent sont définis pour un chargement paresseux, ils seront préchargés en arrière-plan.
    • L'avantage est que lorsque l'utilisateur navigue vers ces routes, les composants sont déjà chargés, offrant une navigation plus rapide.

CONSEIL

La stratégie PreloadAllModules est particulièrement utile pour les petites à moyennes applications où vous voulez combiner les avantages du chargement initial rapide (grâce au lazy loading) et de la navigation rapide ultérieure (grâce au préchargement).

ATTENTION

Bien que le préchargement avec PreloadAllModules puisse améliorer les performances de navigation, il augmente la quantité de données téléchargées initialement. Pour les grandes applications ou les connexions lentes, cela pourrait ne pas être la meilleure approche.

Créer une stratégie de préchargement personnalisée

Parfois, les stratégies de préchargement par défaut d'Angular ne suffisent pas pour répondre aux besoins spécifiques de votre application. C'est là qu'interviennent les stratégies de préchargement personnalisées.

Imaginons un scénario concret : vous avez une application de gestion d'utilisateurs avec un espace administrateur. Vous voulez précharger le module administrateur uniquement pour les utilisateurs connectés ayant des droits d'administrateur.

Voici comment nous pourrions implémenter cette stratégie :

typescript
import { Injectable, inject } from '@angular/core';
import { PreloadingStrategy, Route } from '@angular/router';
import { Observable, of } from 'rxjs';
  // On part du principe que le service existe déjà
import { UserService } from './user.service';

@Injectable({
  providedIn: 'root'
})
export class AdminPreloadingStrategy implements PreloadingStrategy {
  private userService = inject(UserService);

  preload(route: Route, load: () => Observable<any>): Observable<any> {
    if (route.path === 'admin' && this.userService.isAdmin()) {
      return load();
    } else {
      return of(null);
    }
  }
}
typescript
import { Routes } from '@angular/router';

export const routes: Routes = [
  { 
    path: 'users', 
    loadComponent: () => import('./components/features/user/user.component').then(m => m.UserComponent)
  },
  { 
    path: 'admin', 
    loadComponent: () => import('./components/features/admin/admin.component').then(m => m.AdminComponent)
  }
];
typescript
import { ApplicationConfig } from '@angular/core';
import { provideRouter, withPreloading } from '@angular/router';
import { provideHttpClient } from '@angular/common/http';
import { routes } from './app.routes';
import { AdminPreloadingStrategy } from './custom-preloading-strategy';

export const appConfig: ApplicationConfig = {
  providers: [
    provideRouter(routes, withPreloading(AdminPreloadingStrategy)),
    provideHttpClient()
  ]
};

Examinons ce que fait notre stratégie personnalisée :

  1. Nous créons une classe AdminPreloadingStrategy qui implémente l'interface PreloadingStrategy.

  2. Dans la méthode preload, nous vérifions deux conditions :

    • Si la route en cours de traitement est 'admin'
    • Si l'utilisateur actuel est un administrateur (via this.userService.isAdmin())
  3. Si ces deux conditions sont remplies, nous retournons load(), ce qui déclenche le préchargement du composant.

  4. Sinon, nous retournons of(null), ce qui signifie que nous ne préchargeons pas le composant.

  5. Dans app.config.ts, nous utilisons notre stratégie personnalisée avec withPreloading(AdminPreloadingStrategy).

CONSEIL

Cette approche est particulièrement utile lorsque vous avez des sections de votre application qui ne sont pertinentes que pour certains utilisateurs. En préchargeant de manière sélective, vous optimisez l'utilisation des ressources tout en améliorant l'expérience des utilisateurs concernés.

Cette stratégie personnalisée offre plusieurs avantages :

  1. Performance optimisée : Seuls les utilisateurs administrateurs téléchargent le module admin, économisant de la bande passante pour les autres utilisateurs.

  2. Expérience utilisateur améliorée : Les administrateurs bénéficient d'un accès plus rapide à leurs outils, car le module est préchargé.

  3. Sécurité renforcée : Bien que ce ne soit pas une mesure de sécurité en soi, cela ajoute une couche supplémentaire en ne préchargeant pas inutilement du contenu sensible pour les utilisateurs non autorisés.

En créant des stratégies de préchargement personnalisées, vous pouvez adapter finement le comportement de chargement de votre application Angular à vos besoins spécifiques, offrant ainsi une expérience utilisateur optimale tout en gérant efficacement les ressources.

Indiquer si une route doit être préchargée ou non

Dans une application e-commerce, certaines catégories de produits sont souvent plus populaires que d'autres. Nous pouvons utiliser une stratégie de préchargement sélective pour améliorer l'expérience utilisateur en préchargeant ces catégories populaires, tout en laissant les autres se charger à la demande.

Voici comment nous pouvons implémenter cette approche :

typescript
import { Routes } from '@angular/router';

export const routes: Routes = [
  { 
    path: 'electronics', 
    loadComponent: () => import('./components/features/electronics/electronics.component').then(m => m.ElectronicsComponent),
    data: { preload: true }  // Catégorie populaire, à précharger
  },
  { 
    path: 'clothing', 
    loadComponent: () => import('./components/features/clothing/clothing.component').then(m => m.ClothingComponent),
    data: { preload: true }  // Autre catégorie populaire
  },
  { 
    path: 'books', 
    loadComponent: () => import('./components/features/books/books.component').then(m => m.BooksComponent),
    data: { preload: false }  // Catégorie moins populaire, pas de préchargement
  },
  { 
    path: 'furniture', 
    loadComponent: () => import('./components/features/furniture/furniture.component').then(m => m.FurnitureComponent)
    // Pas de data.preload défini, donc pas de préchargement par défaut
  }
];
typescript
import { Injectable } from '@angular/core';
import { PreloadingStrategy, Route } from '@angular/router';
import { Observable, of } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class SelectivePreloadingStrategy implements PreloadingStrategy {
  preload(route: Route, load: () => Observable<any>): Observable<any> {
    if (route.data && route.data['preload']) {
      console.log('Préchargement de la catégorie:', route.path);
      return load();
    } else {
      return of(null);
    }
  }
}
typescript
import { ApplicationConfig } from '@angular/core';
import { provideRouter, withPreloading } from '@angular/router';
import { provideHttpClient } from '@angular/common/http';
import { routes } from './app.routes';
import { SelectivePreloadingStrategy } from './selective-preloading-strategy';

export const appConfig: ApplicationConfig = {
  providers: [
    provideRouter(routes, withPreloading(SelectivePreloadingStrategy)),
    provideHttpClient()
  ]
};

Examinons ce que fait cette approche dans le contexte d'une application e-commerce :

  1. Dans app.routes.ts, nous définissons nos routes pour différentes catégories de produits :

    • Les catégories "electronics" et "clothing" sont marquées avec data: { preload: true }, indiquant qu'elles doivent être préchargées.
    • La catégorie "books" est explicitement marquée pour ne pas être préchargée avec data: { preload: false }.
    • La catégorie "furniture" n'a pas de propriété preload, donc elle ne sera pas préchargée par défaut.
  2. Dans selective-preloading-strategy.ts, notre stratégie vérifie la présence de data.preload sur chaque route :

    • Si route.data['preload'] est true, le composant est préchargé.
    • Sinon, le composant n'est pas préchargé.
  3. Dans app.config.ts, nous appliquons notre SelectivePreloadingStrategy à l'ensemble de l'application.

CONSEIL

Cette approche est particulièrement utile pour les sites e-commerce où certaines catégories de produits sont plus fréquemment visitées que d'autres. En préchargeant ces catégories populaires, vous améliorez l'expérience utilisateur pour la majorité de vos visiteurs, tout en optimisant l'utilisation des ressources.

Utiliser l'API Network Information pour optimiser le préchargement

Le préchargement des modules dans Angular peut considérablement améliorer les performances de navigation. Cependant, sur des connexions lentes ou limitées, cela peut avoir l'effet inverse en consommant inutilement de la bande passante.

Le saviez-vous ?

Le préchargement peut affecter négativement les performances sur les réseaux lents en entrant en concurrence avec d'autres requêtes importantes comme les appels AJAX.

L'API Network Information à la rescousse

L'API Network Information nous permet d'obtenir des informations sur la connexion de l'utilisateur en temps réel. Nous allons l'utiliser pour créer une stratégie de préchargement "consciente du réseau".

Voici comment implémenter cette stratégie :

typescript
import { Injectable } from '@angular/core';
import { PreloadingStrategy, Route } from '@angular/router';
import { Observable, EMPTY } from 'rxjs';

@Injectable({ providedIn: 'root' })
export class NetworkAwarePreloadingStrategy implements PreloadingStrategy {
  preload(route: Route, loadFunction: () => Observable<any>): Observable<any> {
    if (this.isPreloadingAllowed(route)) {
      return loadFunction();
    } else {
      return EMPTY;
    }
  }

  private isPreloadingAllowed(route: Route): boolean {
    const networkInfo = (navigator as any).connection;
    if (networkInfo) {
      // Mode économie de données
      if (networkInfo.saveData) {
        console.log('Préchargement désactivé : mode économie de données actif');
        return false;
      }

      // Connexion lente (2G ou moins)
      const connectionSpeed = networkInfo.effectiveType || '';
      if (connectionSpeed.includes('2g')) {
        console.log('Préchargement désactivé : connexion 2G détectée');
        return false;
      }

      // Vérification du temps de latence (RTT)
      const latency = networkInfo.rtt;
      if (latency && latency > 500) { // 500 ms
        console.log('Préchargement désactivé : latence élevée détectée');
        return false;
      }

      // Vérification de la bande passante
      const bandwidth = networkInfo.downlink;
      if (bandwidth && bandwidth < 1.5) { // Moins de 1.5 Mbps
        console.log('Préchargement désactivé : bande passante faible');
        return false;
      }
    }

    console.log('Préchargement autorisé : conditions réseau favorables');
    return true;
  }
}
typescript
import { ApplicationConfig } from '@angular/core';
import { provideRouter, withPreloading } from '@angular/router';
import { routes } from './app.routes';
import { NetworkAwarePreloadingStrategy } from './core/preload/network';

export const appConfig: ApplicationConfig = {
  providers: [
    provideRouter(routes, withPreloading(NetworkAwarePreloadingStrategy)),
  ]
};
typescript
import { Routes } from '@angular/router';
import { UsersComponent } from './pages/users/users.component';

// on imagine que notre application contient 2 pages :
// - la page d'accueil
// - la page de login
export const routes: Routes = [
    {
        path: '',
        loadComponent: () => import('./pages/users/users.component').then(m => m.UsersComponent)
    },
    {
        path: 'login',
        loadChildren: () => import('./pages/login/login.routes')
            .then(m => m.routes),
    }
];

L'objet networkInfo (accessible via navigator.connection) fournit plusieurs propriétés utiles :

  1. saveData : Indique si le mode économie de données est activé.
  2. effectiveType : Type de connexion (4g, 3g, 2g, slow-2g).
  3. rtt : Round Trip Time en millisecondes.
  4. downlink : Bande passante estimée en Mbps.

En savoir plus

navigator.connection est une propriété de l'objet navigator en JavaScript. Cette propriété est spécifique au navigateur et n'est pas standardisée, ce qui signifie qu'elle n'est pas supportée par tous les navigateurs.

Lisez plus sur navigator.connection ici.

Tester dans le navigateur avec limitation de bande passante

Attention

Cette méthode ne fonctionne que dans les navigateurs qui supportent l'API Network Information. Essayez d'utiliser un navigateur moderne comme Chrome.

Pour tester votre stratégie de préchargement avec différentes conditions réseau :

  1. Ouvrez les DevTools de votre navigateur (F12 ou Ctrl+Shift+I).
  2. Allez dans l'onglet "Network".
  3. Cherchez l'option "Throttling" (généralement en haut de l'onglet).
  4. Choisissez un profil prédéfini (comme "3G") ou créez un profil personnalisé.

Tester une connexion lente

Pour créer un profil personnalisé :

  • Cliquez sur "Add" ou "Custom" dans le menu déroulant Throttling.
  • Définissez la vitesse de téléchargement, la vitesse d'envoi et la latence.

Exemple de profils :

  • Fast 3G: 1.5 Mbps download, 750 Kbps upload, 40ms RTT
  • Slow 3G: 780 Kbps download, 330 Kbps upload, 200ms RTT

Une fois le throttling activé, rechargez votre application et observez comment votre stratégie de préchargement réagit aux différentes conditions réseau simulées. Si vous avez une connexion lente, vous verrez que les composants ne sont pas préchargés. A contrario, si vous avez une connexion rapide, les composants seront préchargés.

CONSEIL

N'oubliez pas de désactiver le throttling après vos tests pour revenir à des conditions normales !

Cas d'usage

Si vous ne voyez pas comment appliquer une stratégie de préchargement à votre application, voici quelques exemples de cas d'usage et les stratégies suggérées :

Cas d'usageDescriptionStratégie suggérée
Application à charge légèrePetite application avec peu de modulesPreloadAllModules
Application à charge lourdeGrande application avec de nombreux modulesStratégie personnalisée basée sur la priorité
Application e-commercePréchargement des catégories populairesStratégie sélective basée sur les données de route
Application mobileOptimisation pour les connexions variablesNetworkAwarePreloadingStrategy
Application avec authentificationPréchargement différent pour utilisateurs connectés/non connectésStratégie personnalisée basée sur l'état d'authentification
Application avec navigation complexePréchargement basé sur la structure de navigationStratégie basée sur la profondeur des routes
Application avec utilisation saisonnièrePréchargement adapté aux périodes de forte affluenceStratégie basée sur le temps/date
Application avec analyse utilisateurPréchargement basé sur le comportement de l'utilisateurStratégie d'apprentissage automatique
Application à forte interaction visuellePréchargement des modules visibles à l'écranStratégie basée sur le viewport
Application avec contenu géolocaliséPréchargement basé sur la localisation de l'utilisateurStratégie personnalisée utilisant la géolocalisation
Application avec mode hors lignePréchargement adapté à la disponibilité du réseauStratégie combinant NetworkAwarePreloadingStrategy et stockage local
Application multilinguePréchargement basé sur la langue de l'utilisateurStratégie personnalisée basée sur les préférences linguistiques
Application avec thèmesPréchargement des ressources spécifiques au thèmeStratégie basée sur les préférences de thème de l'utilisateur
Application avec fonctionnalités premiumPréchargement différencié pour utilisateurs gratuits/premiumStratégie basée sur le type d'abonnement
Application avec mises à jour fréquentesPréchargement des nouveaux modulesStratégie basée sur la version des modules

Les paquets de préchargement

Il existe des paquets de préchargement pour Angular qui peuvent vous aider à optimiser les performances de votre application.

Attention

C'est une liste de paquets non-officiels. Assurez-vous de vérifier leur compatibilité avec votre version d'Angular et avec les navigateurs que vous utilisez.

Il fournit une stratégie de préchargement du routeur qui télécharge automatiquement les modules paresseux associés à tous les liens visibles à l'écran.

https://github.com/mgechev/ngx-quicklink

ngx-hover-preload

Ce paquet exporte une stratégie de préchargement (PreloadingStrategy), qui préchargera un itinéraire chargé paresseusement au passage de la souris sur le lien du routeur correspondant.

https://github.com/mgechev/ngx-hover-preload

Chaque mois, recevez en avant-première notre newsletter avec les dernières actualités, tutoriels, astuces et ressources Angular directement par email !