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.

Nouveautés d'Angular 19 (20 novembre 2024)

L'hydratation incrémentale

Angular 19 introduit une nouvelle fonctionnalité puissante appelée "hydratation incrémentale", qui s'appuie sur les fonctionnalités @defer et Event Replay.

L'hydratation incrémentale est une nouvelle approche qui transforme la manière dont Angular gère le rendu côté serveur (SSR). Voyons en détail ce concept important.

Imaginez une plante déshydratée qui reprend vie progressivement lorsqu'on lui donne de l'eau. C'est similaire à ce qui se passe avec une page web en SSR : d'abord, le navigateur reçoit du HTML statique (la plante déshydratée), puis Angular "réhydrate" cette page en la rendant interactive (la plante reprend vie).

CONCEPT CLÉ

L'hydratation incrémentale permet de contrôler précisément quand et comment les différentes parties de votre application deviennent interactives, plutôt que d'hydrater toute la page d'un coup.

Comment ça fonctionne ?

  1. Phase initiale :

    • Le serveur envoie le HTML statique
    • L'utilisateur voit immédiatement le contenu
    • La page n'est pas encore interactive
  2. Phase d'hydratation :

    • Les blocs de contenu sont hydratés selon différents déclencheurs
    • Certaines parties restent temporairement non interactives
    • L'hydratation se fait progressivement

IMPORTANT

Le contenu reste visible même s'il n'est pas encore interactif. C'est une amélioration majeure par rapport au chargement paresseux traditionnel.

Incremental hydrationSur cette image, on voit que le contenu est déjà interactif, mais pas tout.

Activation de la fonctionnalité

Pour activer cette fonctionnalité en preview dans Angular 19 :

ts
bootstrapApplication(AppComponent, {
  providers: [provideClientHydration(withIncrementalHydration())]
});

Utilisation avec @defer

La syntaxe utilise le bloc @defer avec une nouvelle option hydrate :

ts
@Component({
  selector: 'app-user-list',
  template: `
    @defer(on timer(15s); hydrate on interaction) {
      <app-users />
    } @placeholder {
      <p>Chargement de la liste des utilisateurs...</p>
    }
  `
})
export class UserListComponent {
  // ... code du composant
}
ts
@Component({
  selector: 'app-users',
  template: `
    <h1>Utilisateurs</h1>
    <button (click)="refreshUsers()">Rafraîchir la liste</button>
    @for (user of users(); track user.id) {
      <div>{{ user.name }}</div>
    }
  `
})
export class UsersComponent {
  // ... code du composant
}

Standalone par défaut

Dans la version 19, les développeurs d'Angular franchiront une nouvelle étape en activant par défaut l'option standalone dans les composants, directives et pipes, vous n'aurez donc plus besoin de taper "standalone: true".

Lire la suite: Standalone par défaut

Nouvelle API pour charger les ressources

La nouvelle API Resource d'Angular 19 simplifie la gestion des requêtes HTTP et l'état des données. Cette fonctionnalité révolutionne la façon dont nous gérons les données dans nos applications.

AVANTAGE CLÉ

L'API Resource permet de gérer automatiquement l'état de chargement, les erreurs et les données, sans avoir à écrire du code complexe de gestion d'état.

États disponibles

L'API Resource fournit plusieurs états possibles via le signal status() :

  • ResourceStatus.Idle : état initial
  • ResourceStatus.Loading : pendant le chargement initial
  • ResourceStatus.Error : en cas d'erreur
  • ResourceStatus.Resolved : données chargées avec succès
  • ResourceStatus.Reloading : pendant un rechargement
  • ResourceStatus.Local : quand la valeur est définie localement

Voici un exemple complet qui utilise ces différents états :

ts
@Component({
  selector: 'app-user',
  template: `
    @switch (userResource.status()) {
      @case (0) {
        <p>En attente...</p>
      }
      @case (resourceStatus.Idle) {
        <p>Chargement initial...</p>
      }
      @case (resourceStatus.Reloading) {
        <div>
          <p>Mise à jour...</p>
          <div>{{ userResource.value().name }}</div>
        </div>
      }
      @case (resourceStatus.Error) {
        <p>Erreur : {{ userResource.error() }}</p>
      }
      @case (resourceStatus.Resolved) {
        <div>
          <h2>{{ userResource.value().name }}</h2>
          <button (click)="userResource.reload()">Recharger</button>
        </div>
      }
    }
  `
})
export class UserComponent {
  resourceStatus = ResourceStatus
  userResource = resource({
    loader: async () => {
      const response = await fetch('https://jsonplaceholder.typicode.com/users');
      return response.json() as Promise<any>;
    }
  });
}

FONCTIONNALITÉS

L'API Resource fournit un objet ResourceRef avec les signaux suivants :

  • value() : contient le résultat de la promesse
  • isLoading() : indique si la ressource est en cours de chargement
  • error() : contient l'erreur si la promesse est rejetée
  • status() : indique l'état de la ressource
  • reload() : méthode pour recharger manuellement la ressource

Voici un exemple concret avec notre gestion d'utilisateurs :

ts
import { Component, ResourceStatus } from '@angular/core';
import { resource } from '@angular/core';
import { User } from './user.interface';

@Component({
  selector: 'app-user',
  template: `
    @if (userResource.status() === resourceStatus.Loading) {
      <p>Chargement en cours...</p>
    } @else if (userResource.error()) {
      <p>Erreur : {{ userResource.error() }}</p>
    } @else {
      <div>
        <h2>{{ userResource.value().name }}</h2>
        <p>{{ userResource.value().email }}</p>
      </div>
    }
  `
})
export class UserComponent {
  resourceStatus = ResourceStatus
  userResource = resource({
    loader: () => {
      return fetch('https://api.example.com/users/1')
        .then(res => res.json() as Promise<User>);
    }
  });
}
ts
import { Component, ResourceStatus } from '@angular/core';
import { resource } from '@angular/core';
import { User } from './user.interface';

@Component({
  selector: 'app-user-list',
  template: `
    @if (usersResource.status() === resourceStatus.Loading) {
      <p>Chargement des utilisateurs...</p>
    } @else {
      @for (user of usersResource.value(); track user.id) {
        <div>{{ user.name }}</div>
      }
    }
  `
})
export class UserListComponent {
  resourceStatus = ResourceStatus
  usersResource = resource({
    request: this.searchTerm,
    loader: ({ request: term }) => {
      return fetch(`https://api.example.com/users?search=${term}`)
        .then(res => res.json() as Promise<User[]>);
    }
  });
}

FONCTIONNALITÉS

L'API Resource fournit un objet ResourceRef avec les signaux suivants :

  • value() : contient le résultat de la promesse
  • isLoading() : indique si la ressource est en cours de chargement
  • error() : contient l'erreur si la promesse est rejetée
  • status() : indique l'état de la ressource ('loading', 'success', 'error')

COMPATIBILITÉ

La fonction resource() n'est pas liée à RxJS et peut utiliser n'importe quel client retournant des promesses. Pour les clients basés sur les Observables, utilisez plutôt rxResource(). C'est un exemple supplémentaire du découplage d'Angular avec RxJS, tout en gardant une interopérabilité fluide.

L'API Resource permet également de réagir aux changements de paramètres et de mettre à jour automatiquement les données :

ts
export class UserSearchComponent {
  searchInput = signal('');
  userResource = resource({
    request: this.searchInput,
    loader: ({ request: searchTerm }) => {
      return fetch(
        `https://api.example.com/users?search=${searchTerm}`
      ).then(res => res.json() as Promise<User[]>);
    }
  });
}

Usage de linkedSignal()

Le linkedSignal() est une nouvelle fonctionnalité expérimentale qui permet de créer un signal modifiable (writable) qui peut réagir aux changements d'un autre signal source.

Imaginez un thermostat intelligent qui se réinitialise à une température par défaut chaque fois que vous changez de pièce, mais que vous pouvez toujours ajuster manuellement dans chaque pièce. C'est exactement ce que fait linkedSignal() !

CONCEPT CLÉ

linkedSignal() combine les avantages d'un signal modifiable avec la capacité de se réinitialiser automatiquement en fonction d'un signal source.

Voici un exemple concret avec notre gestion d'utilisateurs :

ts
import { Component } from '@angular/core';
import { signal, linkedSignal } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';

@Component({
  selector: 'app-root',
  template: `
      <div>
        <h2>{{ selectedUser().name }}</h2>
        <p>Nombre d'éléments par page : {{ itemsPerPage() }}</p>
        <button (click)="updateItemsPerPage(itemsPerPage() + 5)">
          Augmenter
        </button>
        <button (click)="changeUser()">Changer User</button>
      </div>
  `,
})
export class AppComponent {
  // Signal source
  selectedUser = signal<any>({
    name: 'ana'
  });

  // Signal lié qui se réinitialise quand l'utilisateur change
  itemsPerPage = linkedSignal({
    source: this.selectedUser,
    computation: () => 10, // Valeur par défaut
  });

  updateItemsPerPage(value: number) {
    this.itemsPerPage.set(value);
  }

  changeUser() {
    this.selectedUser.set({
      name: 'ben'
    })
  }
}

EXPERIMENTAL

Cette fonctionnalité est expérimentale dans Angular 19. La syntaxe pourrait évoluer dans les versions futures.

Cas d'utilisation avec les requêtes HTTP

linkedSignal() peut également être utilisé avec des requêtes HTTP pour créer des signaux modifiables à partir de données distantes :

ts
import { Component, Injectable } from '@angular/core';
import { linkedSignal } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import { HttpClient } from '@angular/common/http';
import { inject } from '@angular/core';
import { toSignal } from '@angular/core/rxjs-interop';

interface User {
  id: number
  name: string
}

@Injectable({
  providedIn: 'root',
})
export class UserService {
  http = inject(HttpClient)

  // Récupération des données
  httpUsers = toSignal(this.http.get<User[]>('https://jsonplaceholder.typicode.com/users'));

  // Signal modifiable lié aux données HTTP
  editableUsers = linkedSignal({
    source: this.httpUsers,
    computation: (users) => users || [] // Valeur par défaut si null
  });

  // Peut être modifié localement
  addUser(newUser: Partial<User>) {
    const currentUsers: any = this.editableUsers();
    this.editableUsers.set([...currentUsers, newUser]);
  }
}

@Component({
  selector: 'app-root',
  template: `
    <div>
       @for (user of users() ; track user.id) {
          <p>{{ user.name }}</p>
       }
       <button (click)="addUser({ name: 'test '})">Ajouter un utilisateur</button>
    </div>
  `,
})
export class AppComponent {
  private userService = inject(UserService)
  users = this.userService.editableUsers
  addUser = this.userService.addUser.bind(this.userService)
}

Migration automatique vers les signaux

Angular 19 introduit un outil de migration automatique pour convertir vos composants vers l'utilisation des signaux. Cette fonctionnalité simplifie grandement la transition vers les nouvelles APIs stables de signal inputs, view queries et content queries.

PRODUCTIVITÉ

La commande de migration permet de moderniser rapidement votre code existant sans avoir à le réécrire manuellement !

Comment utiliser l'outil de migration

Pour lancer la migration, utilisez la commande :

bash
ng generate @angular/core:signals

Vous aurez alors plusieurs options de migration :

  • Conversion des @Input vers les input() basés sur les signaux
  • Conversion des @Output vers la nouvelle fonction output()
  • Conversion des @ViewChild/@ViewChildren et @ContentChild/@ContentChildren vers leurs équivalents en signaux
ts
@Component({
  selector: 'app-user',
  template: `
    <h2>{{ name }}</h2>
    <button (click)="update.emit()">Mettre à jour</button>
  `
})
export class UserComponent {
  @Input() name: string;
  @Output() update = new EventEmitter<void>();
  @ViewChild('title') title: ElementRef;
}
ts
@Component({
  selector: 'app-user',
  template: `
    <h2>{{ name() }}</h2>
    <button (click)="update()">Mettre à jour</button>
  `
})
export class UserComponent {
  name = input<string>();
  update = output<void>();
  title = viewChild<ElementRef>('title');
}

LIMITATIONS

La migration est conservative par défaut. Si elle ne peut pas migrer quelque chose sans risquer de casser le build, elle laissera le code inchangé.

Mode "best-effort"

Pour une migration plus agressive, vous pouvez utiliser l'option --best-effort-mode :

bash
ng generate @angular/core:signals --best-effort-mode

Si vous voulez comprendre pourquoi certaines migrations n'ont pas été effectuées, utilisez l'option --insert-todos :

ts
// TODO: Non migré car :
// Les inputs avec accesseurs ne peuvent pas être migrés car trop complexes
@Input()
set name(value: string) {
  this._name = value;
}

CONSEIL

La migration fonctionne remarquablement bien pour la plupart des cas simples. Pour les cas plus complexes, vous devrez peut-être faire quelques ajustements manuels.

Support IDE

Une excellente nouvelle : vous pourrez bientôt déclencher la migration directement depuis votre IDE pour un fichier ou une propriété spécifique, grâce à la nouvelle version du service de langage Angular !

NOTE

La migration convertit la syntaxe mais ne refactorise pas automatiquement le code pour utiliser computed() à la place de ngOnChanges ou d'autres patterns avancés de signaux.

Nouveaux initialisateurs d'application

Angular 19 introduit une nouvelle façon plus élégante de gérer l'initialisation de votre application avec provideAppInitializer, provideEnvironmentInitializer et providePlatformInitializer.

Imaginez que vous préparez une recette de cuisine : avant de commencer à cuisiner, vous devez vous assurer que tous vos ingrédients sont prêts et que votre plan de travail est organisé. C'est exactement ce que font ces initialisateurs !

CONCEPT CLÉ

Les initialisateurs permettent d'exécuter du code avant que votre application ne démarre complètement, comme charger des configurations ou initialiser des services essentiels.

Migration vers provideAppInitializer

Voici comment migrer de l'ancien APP_INITIALIZER vers le nouveau provideAppInitializer :

ts
import { APP_INITIALIZER } from '@angular/core';

export const appConfig: ApplicationConfig = {
  providers: [
    {
      provide: APP_INITIALIZER,
      useValue: () => inject(MyService).init(),
      multi: true
    }
  ]
};
ts
import { provideAppInitializer } from '@angular/core';

export const appConfig: ApplicationConfig = {
  providers: [
    provideAppInitializer(() => inject(MyService).init())
  ]
};

DÉPRÉCIATION

Les tokens APP_INITIALIZER, ENVIRONMENT_INITIALIZER et PLATFORM_INITIALIZER sont désormais dépréciés. Utilisez plutôt :

  • provideAppInitializer
  • provideEnvironmentInitializer
  • providePlatformInitializer

NOTE

La commande ng update s'occupera automatiquement de migrer vos initialisateurs vers la nouvelle syntaxe. Cependant, vous pourriez vouloir refactoriser manuellement pour profiter pleinement de la nouvelle API avec inject().

Je vais ajouter une section sur les nouvelles fonctionnalités du Router dans Angular 19. Voici la partie à ajouter :

Partage de données via RouterOutlet

Angular 19 introduit une nouvelle façon élégante de partager des données entre les composants parents et enfants via le RouterOutlet. Cette fonctionnalité simplifie grandement la communication entre les composants routés.

CONCEPT CLÉ

routerOutletData permet de passer des données du composant parent vers tous ses enfants routés, et ces données restent réactives grâce aux signaux.

Voici un exemple concret avec notre gestion d'utilisateurs :

ts
@Component({
  selector: 'app-parent',
  template: `
    <h1>Gestion des utilisateurs</h1>
    <router-outlet [routerOutletData]="userModel"></router-outlet>
  `
})
export class ParentComponent {
  userModel = {
    id: 1,
    name: 'John Doe',
    username: 'johndoe',
    email: '[email protected]'
  };
}
ts
@Component({
  selector: 'app-child',
  template: `
    <div>
      <h2>Détails de l'utilisateur</h2>
      <p>Nom : {{ userModel().name }}</p>
      <p>Email : {{ userModel().email }}</p>
    </div>
  `
})
export class ChildComponent {
  readonly userModel = inject<Signal<User>>(ROUTER_OUTLET_DATA);
}

RÉACTIVITÉ

La grande nouveauté est que les données sont transmises sous forme de signal ! Cela signifie que si les données changent dans le composant parent, elles seront automatiquement mises à jour dans tous les composants enfants.

Je vais ajouter une section sur le Service Worker. Voici la partie à ajouter :

Service Worker amélioré

Angular 19 apporte des améliorations significatives au Service Worker, notamment pour la gestion du cache et le rafraîchissement anticipé des ressources.

Imaginez un majordome (le Service Worker) qui gère intelligemment le stockage de vos documents (ressources web). Non seulement il sait quand les documents deviennent périmés, mais il peut aussi anticiper leur mise à jour avant qu'ils ne le soient !

Configuration de l'âge maximal de l'application

Une nouvelle option applicationMaxAge permet de définir une durée de vie maximale pour l'ensemble de l'application en cache :

json
{
  "applicationMaxAge": "1d6h",
  "assetGroups": [
    {
      "name": "app",
      "installMode": "prefetch",
      "resources": {
        "files": [
          "/favicon.ico",
          "/index.html",
          "/*.css",
          "/*.js"
        ]
      }
    }
  ]
}

UTILITÉ

Cette fonctionnalité est particulièrement utile pour s'assurer qu'un utilisateur revenant après une longue période obtienne la dernière version de l'application, plutôt qu'une version obsolète mise en cache.

Rafraîchissement anticipé avec refreshAhead

La nouvelle option refreshAhead permet de rafraîchir proactivement les ressources avant leur expiration :

json
{
  "dataGroups": [
    {
      "name": "api-users",
      "urls": ["/api/users/**"],
      "cacheConfig": {
        "maxAge": "1d",
        "timeout": "10s",
        "refreshAhead": "10m"
      }
    }
  ]
}

FONCTIONNEMENT

Avec refreshAhead: "10m", si une ressource expire dans moins de 10 minutes, le Service Worker la rafraîchira automatiquement en arrière-plan, garantissant ainsi que l'utilisateur a toujours accès à des données à jour.

Dans notre application de gestion d'utilisateurs, cela pourrait être configuré ainsi :

json
{
  "applicationMaxAge": "7d",
  "dataGroups": [
    {
      "name": "user-api",
      "urls": [
        "/api/users",
        "/api/users/*"
      ],
      "cacheConfig": {
        "maxAge": "1d",
        "timeout": "5s",
        "refreshAhead": "30m",
        "strategy": "freshness"
      }
    },
    {
      "name": "user-assets",
      "urls": [
        "/assets/user-avatars/*"
      ],
      "cacheConfig": {
        "maxAge": "7d",
        "timeout": "10s",
        "refreshAhead": "2h",
        "strategy": "performance"
      }
    }
  ]
}

IMPORTANT

Le Service Worker ne sera activé qu'en production. En développement, assurez-vous de tester votre configuration en utilisant ng serve --configuration=production.

BONNES PRATIQUES

  • Utilisez des durées plus courtes pour les données API (maxAge: "1d")
  • Définissez des durées plus longues pour les assets statiques (maxAge: "7d")
  • Ajustez refreshAhead en fonction de la criticité des données

Server-Side Rendering (SSR) amélioré

Event Replay stable

La fonctionnalité Event Replay, introduite en version 18, est maintenant stable. La CLI génère automatiquement l'appel withEventReplay() nécessaire lorsque vous créez une nouvelle application avec SSR.

CONCEPT CLÉ

Event Replay permet de capturer les événements utilisateur qui se produisent pendant l'hydratation et de les rejouer une fois l'application complètement chargée, garantissant ainsi qu'aucune interaction n'est perdue.

Stabilité de l'application

Angular 19 introduit un nouveau service stable PendingTasks (anciennement ExperimentPendingTasks) pour gérer la stabilité de l'application en SSR, particulièrement utile dans les applications sans Zone.js.

Voici comment l'utiliser dans notre application de gestion d'utilisateurs :

ts
import { Injectable, inject } from '@angular/core';
import { PendingTasks } from '@angular/core';
import { User } from './user.interface';

@Injectable({
  providedIn: 'root'
})
export class UserService {
  private pendingTasks = inject(PendingTasks);
  
  async getUsers(): Promise<User[]> {
    // Angular attendra que cette promesse soit résolue
    return this.pendingTasks.run(async () => {
      const response = await fetch('/api/users');
      return response.json();
    });
  }
}
ts
import { Component, inject } from '@angular/core';
import { toSignal } from '@angular/core/rxjs-interop';
import { pendingUntilEvent } from '@angular/core/rxjs-interop';
import { UserService } from './user.service';

@Component({
  selector: 'app-user-list',
  template: `
    @if (users()) {
      @for (user of users(); track user.id) {
        <div>{{ user.name }}</div>
      }
    }
  `
})
export class UserListComponent {
  private userService = inject(UserService);
  users = toSignal(
    this.userService.getUsers().pipe(
      pendingUntilEvent()
    )
  );
}

NOUVEAUTÉS

  1. PendingTasks.run() : nouvelle méthode simplifiée pour gérer les tâches asynchrones
  2. pendingUntilEvent() : nouvel opérateur RxJS expérimental pour marquer un observable comme important jusqu'à sa première émission

MIGRATION

La commande ng update s'occupera automatiquement de migrer ExperimentPendingTasks vers PendingTasks. Assurez-vous de lancer la mise à jour pour en bénéficier.

Bonnes pratiques SSR

Pour optimiser votre application SSR avec Angular 19 :

  1. Utilisez PendingTasks pour les opérations asynchrones critiques
  2. Activez withEventReplay() pour une meilleure expérience utilisateur
  3. Utilisez pendingUntilEvent() pour les observables importants
  4. Testez votre application en SSR avec ng serve --ssr

Rendu Hybride dans Angular 19

Le rendu hybride est une nouvelle fonctionnalité majeure d'Angular 19 qui permet de définir différentes stratégies de rendu pour chaque route de votre application. Cette approche flexible vous permet d'optimiser le rendu de vos pages selon vos besoins spécifiques.

Les modes de rendu disponibles

Angular 19 propose trois modes de rendu :

  • RenderMode.Prerender : génère la page au moment du build
  • RenderMode.Server : génère la page côté serveur à chaque requête
  • RenderMode.Client : génère la page côté client

Configuration du rendu hybride

Pour utiliser le rendu hybride, vous devez créer un fichier de configuration côté serveur. Voici comment procéder :

ts
const routes: Routes = [
  { path: '', component: HomeComponent },
  { path: 'about', component: AboutComponent },
  { path: 'users/:id', component: UserComponent }
];
ts
import { RenderMode } from '@angular/platform-server';

export const serverRoutes = [
  {
    route: '',
    renderMode: RenderMode.Prerender
  },
  {
    route: 'about',
    renderMode: RenderMode.Prerender
  },
  {
    route: 'users/:id',
    renderMode: RenderMode.Server,
    getPrerenderParams: () => [
      { id: '1' },
      { id: '2' }
    ]
  }
];

IMPORTANT

Pour les routes avec paramètres qui utilisent le pré-rendu, vous devez définir la fonction getPrerenderParams qui spécifie les valeurs de paramètres à pré-rendre.

Configuration du projet

Pour activer le rendu hybride dans votre projet :

bash
ng add @angular/ssr --server-routing

Cette commande va :

  1. Ajouter la configuration nécessaire
  2. Créer le fichier app.routes.server.ts
  3. Configurer le mode de sortie dans angular.json

CONFIGURATION

Dans angular.json, le outputMode peut être :

  • server : pour le SSR et le rendu hybride (déploiement sur Node.js)
  • static : pour le CSR et le SSG (déploiement statique)

Exemple concret avec notre application

Voici comment configurer le rendu hybride pour notre application de gestion d'utilisateurs :

ts
const routes: Routes = [
  { 
    path: '', 
    component: HomeComponent 
  },
  { 
    path: 'users', 
    component: UserListComponent 
  },
  { 
    path: 'users/:id', 
    component: UserDetailComponent 
  },
  { 
    path: 'admin', 
    component: AdminComponent 
  }
];
ts
import { RenderMode } from '@angular/platform-server';

export const serverRoutes = [
  {
    // Page d'accueil : pré-rendue car statique
    route: '',
    renderMode: RenderMode.Prerender
  },
  {
    // Liste des utilisateurs : rendue côté serveur car dynamique
    route: 'users',
    renderMode: RenderMode.Server
  },
  {
    // Détails utilisateur : pré-rendu pour certains utilisateurs
    route: 'users/:id',
    renderMode: RenderMode.Prerender,
    getPrerenderParams: () => [
      { id: '1' }, // VIP users
      { id: '2' }
    ]
  },
  {
    // Admin : rendu côté client car nécessite une authentification
    route: 'admin',
    renderMode: RenderMode.Client
  }
];

BONNES PRATIQUES

  • Utilisez Prerender pour les pages statiques
  • Utilisez Server pour les pages avec contenu dynamique fréquent
  • Utilisez Client pour les pages nécessitant une authentification
  • Pré-rendez les pages les plus visitées pour optimiser les performances

Migration vers le rendu hybride

Si vous avez une application existante utilisant SSR, voici les étapes de migration :

  1. Installez le support du rendu hybride :
bash
ng add @angular/ssr --server-routing
  1. Créez le fichier de configuration serveur :
bash
ng generate @angular/core:server-routes
  1. Mettez à jour angular.json pour utiliser le nouveau mode de sortie :
json
{
  "projects": {
    "your-app": {
      "architect": {
        "build": {
          "options": {
            "outputMode": "server"
          }
        }
      }
    }
  }
}

COMPATIBILITÉ

Les options prerender et appShell sont dépréciées si vous utilisez outputMode. Assurez-vous de migrer vers la nouvelle configuration.

Exemple complet avec notre application

Voici un exemple complet qui combine toutes ces fonctionnalités :

ts
import { ServerRoute, RenderMode, PrerenderFallback } from '@angular/platform-server';
import { inject } from '@angular/core';
import { UserService } from './user.service';

export const serverRoutes: Array<ServerRoute> = [
  {
    // Page d'accueil pré-rendue
    path: '',
    renderMode: RenderMode.Prerender
  },
  {
    // Liste des utilisateurs avec cache
    path: 'users',
    renderMode: RenderMode.Server,
    headers: {
      'Cache-Control': 'max-age=3600'
    }
  },
  {
    // Détails utilisateur avec pré-rendu sélectif
    path: 'users/:id',
    renderMode: RenderMode.Prerender,
    fallback: PrerenderFallback.Server,
    async getPrerenderParams() {
      const userService = inject(UserService);
      const vipUserIds = await userService.getVipUserIds();
      return vipUserIds.map(id => ({ id: id.toString() }));
    }
  },
  {
    // Page 404 personnalisée
    path: '404',
    renderMode: RenderMode.Server,
    status: 404,
    headers: {
      'Cache-Control': 'no-store'
    }
  },
  {
    // Toutes les autres routes en CSR
    path: '**',
    renderMode: RenderMode.Client
  }
];

Injection de dépendances pour les requêtes/réponses

Angular 19 simplifie l'accès aux objets de requête et de réponse pendant le SSR grâce à de nouveaux tokens d'injection.

CONCEPT CLÉ

Ces nouveaux tokens permettent d'accéder facilement aux informations de la requête HTTP et de personnaliser la réponse directement depuis vos composants.

Voici les nouveaux tokens disponibles :

  • REQUEST : accès à l'objet de requête HTTP actuel
  • REQUEST_CONTEXT : passage de métadonnées personnalisées
  • RESPONSE_INIT : accès aux options d'initialisation de la réponse

Exemple d'utilisation dans notre application :

ts
import { Component, inject } from '@angular/core';
import { REQUEST, REQUEST_CONTEXT, RESPONSE_INIT } from '@angular/core';

@Component({
  selector: 'app-user',
  template: `
    <div>
      <h2>Détails utilisateur</h2>
      <p>IP Client : {{ clientIp }}</p>
    </div>
  `
})
export class UserComponent {
  private request = inject(REQUEST);
  private context = inject(REQUEST_CONTEXT);
  private responseInit = inject(RESPONSE_INIT);
  
  clientIp = this.request.headers['x-forwarded-for'];

  constructor() {
    // Définir des en-têtes de réponse
    this.responseInit.headers = {
      'Cache-Control': 'max-age=3600'
    };
    
    // Accéder au contexte personnalisé
    console.log('User Role:', this.context.userRole);
  }
}
ts
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';
import { REQUEST_CONTEXT } from '@angular/core';

export function bootstrap() {
  return bootstrapApplication(AppComponent, {
    providers: [
      {
        provide: REQUEST_CONTEXT,
        useValue: {
          userRole: 'admin',
          region: 'EU'
        }
      }
    ]
  });
}

Améliorations de la CLI

Angular CLI 19 apporte plusieurs améliorations majeures pour le développement.

Hot Module Replacement (HMR) par défaut

Le HMR est maintenant activé par défaut pour les styles lors de l'utilisation de ng serve !

AVANTAGE CLÉ

Vous pouvez modifier vos styles et voir les résultats en temps réel sans recharger la page et sans reconstruire l'application.

Pour activer le HMR expérimental pour les templates :

bash
NG_HMR_TEMPLATES=1 ng serve

Tests avec esbuild

Karma peut maintenant utiliser esbuild au lieu de Webpack, offrant de meilleures performances. Pour l'activer, modifiez votre angular.json :

json
{
  "projects": {
    "your-app": {
      "architect": {
        "test": {
          "builder": "@angular-devkit/build-angular:karma",
          "options": {
            "builderMode": "application"
          }
        }
      }
    }
  }
}

PERFORMANCE

Les tests sont environ 40% plus rapides avec esbuild !

Génération de composants avec export par défaut

Une nouvelle option --export-default a été ajoutée à la commande ng generate component :

bash
ng generate component admin --export-default

Cela génère :

ts
export default class AdminComponent {
  // ...
}

Ce qui simplifie le chargement paresseux :

ts
{
  path: 'admin',
  loadComponent: () => import('./admin/admin.component')
}

Configuration de sécurité CSP stricte

Angular 19 permet d'activer automatiquement une politique de sécurité du contenu (CSP) stricte. Pour l'activer, modifiez votre angular.json :

json
{
  "projects": {
    "your-app": {
      "architect": {
        "build": {
          "options": {
            "security": {
              "autoCsp": true
            }
          }
        }
      }
    }
  }
}

SÉCURITÉ

Cette option applique les meilleures pratiques de sécurité et génère automatiquement des hachages CSP basés sur les scripts de votre application.

Gestion des avertissements Sass

Vous pouvez maintenant configurer la gestion des avertissements de dépréciation Sass :

json
{
  "projects": {
    "your-app": {
      "architect": {
        "build": {
          "options": {
            "stylePreprocessorOptions": {
              "sass": {
                "silenceDeprecations": ["import"],
                "fatalDeprecations": true,
                "futureDeprecations": true
              }
            }
          }
        }
      }
    }
  }
}

UTILITÉ

Cette fonctionnalité est particulièrement utile pour préparer la migration vers Sass v3, qui supprimera certaines APIs dépréciées.

Mode sans Zone.js

Angular 19 introduit une option expérimentale pour créer des projets sans Zone.js :

bash
ng new my-app --experimental-zoneless

EXPÉRIMENTAL

Cette fonctionnalité est encore expérimentale. Assurez-vous de bien comprendre les implications avant de l'utiliser en production.

Les tests unitaires sont également générés avec les providers appropriés pour fonctionner sans Zone.js

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