Explorez toutes les nouveautés Angular 19 : variables @let, route render mode, HMR des styles, signal-based forms, standalone par défaut et améliorations.
Résumé des changements Angular 19
Angular 19 consolide l'écosystème des signaux et améliore drastiquement la Developer Experience. Le focus est sur la flexibilité du rendu, la productivité et la finalisation des APIs expérimentales.
linkedSignal()/resource() stables. Idéal pour les apps SSR complexes.
| Fonctionnalité | Statut Angular 19 | Impact |
|---|---|---|
@let en template | Stable | Moins de propriétés de composant |
| Route render mode | Stable | SSG/SSR/CSR par route |
linkedSignal() | Stable (était expérimental) | Signal dérivé ET modifiable |
resource() | Stable (était expérimental) | HTTP déclaratif avec signaux |
| Hydration incrémentale | Stable | @defer + SSR = LCP optimisé |
| HMR styles | Stable | CSS sans rechargement |
| Effect cleanup | Stable | Nettoyage des effets |
| Standalone par défaut CLI | Stable | Fin de NgModule obligatoire |
Variable de template @let
@let permet de déclarer des variables locales directement dans le template HTML, sans avoir à créer des propriétés intermédiaires dans le composant.
Avant (Angular 18 et avant) :
// Composant
get fullName() { return `${this.user?.firstName} ${this.user?.lastName}`; }
// Template
<p>{{ fullName }}</p>
Après (Angular 19 avec @let) :
@let user = currentUser();
@let fullName = user.firstName + ' ' + user.lastName;
@if (user) {
<h2>{{ fullName }}</h2>
<p>{{ user.email }}</p>
}
@let est scoped au bloc où elle est déclarée. Elle n'est pas accessible en dehors du @if ou @for parent.
Route render mode (SSR/SSG/CSR par route)
Angular 19 permet de définir un mode de rendu différent pour chaque route. Plus besoin de choisir entre "tout SSR" ou "tout CSR".
import { RenderMode, ServerRoute } from '@angular/ssr';
export const serverRoutes: ServerRoute[] = [
{
path: '',
renderMode: RenderMode.Prerender // SSG : généré à la build
},
{
path: 'blog',
renderMode: RenderMode.Prerender // SSG : liste d'articles statique
},
{
path: 'blog/:slug',
renderMode: RenderMode.Server // SSR : rendu à chaque requête (contenu dynamique)
},
{
path: 'dashboard',
renderMode: RenderMode.Client // CSR : rendu côté client uniquement (authentifié)
},
{
path: 'profile',
renderMode: RenderMode.Client // CSR : données utilisateur personnalisées
}
];
Configuration dans app.config.server.ts
// app.config.server.ts
import { mergeApplicationConfig, ApplicationConfig } from '@angular/core';
import { provideServerRendering } from '@angular/platform-server';
import { provideServerRoutesConfig } from '@angular/ssr';
import { appConfig } from './app.config';
import { serverRoutes } from './app.routes.server';
const serverConfig: ApplicationConfig = {
providers: [
provideServerRendering(),
provideServerRoutesConfig(serverRoutes)
]
};
export const config = mergeApplicationConfig(appConfig, serverConfig);
| Mode | Génération | SEO | Cas d'usage |
|---|---|---|---|
Prerender (SSG) | À la build | Maximal | Pages statiques, blog, marketing |
Server (SSR) | À chaque requête | Bon | Données dynamiques, contenu frais |
Client (CSR) | Dans le navigateur | Limité | Dashboards, pages authentifiées |
linkedSignal() — signal dérivé modifiable
linkedSignal() comble un manque : un signal qui dérive d'une source ET reste modifiable. computed() est en lecture seule. linkedSignal() peut être modifié via .set() tout en se réinitialisant quand la source change.
import { signal, linkedSignal } from '@angular/core';
// Cas d'usage : options de pagination liées au nombre de résultats
const totalResults = signal(0);
// pageSize suit totalResults mais peut être modifié par l'utilisateur
const pageSize = linkedSignal(() => {
return totalResults() > 100 ? 25 : 10; // Adapte automatiquement
});
console.log(pageSize()); // 10 (totalResults = 0)
totalResults.set(150);
console.log(pageSize()); // 25 (réinitialisé par la source)
// L'utilisateur peut quand même le modifier
pageSize.set(50);
console.log(pageSize()); // 50
// Mais si totalResults change à nouveau, pageSize se réinitialise
totalResults.set(50);
console.log(pageSize()); // 10 (réinitialisé car source a changé)
linkedSignal() avec computation complète
import { signal, linkedSignal } from '@angular/core';
// Cas avancé : item sélectionné lié à une liste
const items = signal(['Apple', 'Banana', 'Cherry']);
const selectedIndex = signal(0);
const selectedItem = linkedSignal({
source: () => ({ items: items(), index: selectedIndex() }),
computation: ({ items, index }) => items[index] ?? null
});
console.log(selectedItem()); // 'Apple'
selectedIndex.set(2);
console.log(selectedItem()); // 'Cherry'
// L'utilisateur peut forcer une valeur différente
selectedItem.set('Override');
console.log(selectedItem()); // 'Override'
// Mais si source change, recalcul automatique
items.set(['X', 'Y', 'Z']);
console.log(selectedItem()); // 'Z' (recalculé)
resource() — chargement async déclaratif
resource() est une API déclarative pour charger des données asynchrones basée sur des signaux. Elle gère automatiquement les états de chargement, d'erreur, et annule les requêtes obsolètes quand les dépendances changent.
import { Component, signal, resource } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { inject } from '@angular/core';
import { firstValueFrom } from 'rxjs';
interface Article {
id: number;
title: string;
body: string;
}
@Component({
selector: 'app-articles',
standalone: true,
template: `
<input [value]="searchQuery()"
(input)="searchQuery.set($event.target.value)"
placeholder="Rechercher...">
@if (articles.isLoading()) {
<p>Chargement...</p>
} @else if (articles.error()) {
<p class="text-danger">Erreur : {{ articles.error() }}</p>
} @else {
<ul>
@for (article of articles.value(); track article.id) {
<li>{{ article.title }}</li>
}
</ul>
}
`
})
export class ArticlesComponent {
private http = inject(HttpClient);
searchQuery = signal('');
// resource() recharge automatiquement si searchQuery change
articles = resource({
request: () => ({ q: this.searchQuery() }), // Paramètres réactifs
loader: async ({ request, abortSignal }) => {
// abortSignal annule la requête si la source change
return firstValueFrom(
this.http.get<Article[]>(`/api/articles?q=${request.q}`)
);
}
});
}
Propriétés disponibles sur resource()
// Les propriétés d'un resource sont des signaux
const articles = resource({ ... });
articles.value() // Article[] | undefined — données chargées
articles.isLoading() // boolean — chargement en cours
articles.error() // unknown — erreur si échouée
articles.status() // 'idle' | 'loading' | 'resolved' | 'error' | 'reloading'
// Méthodes
articles.reload() // Forcer un rechargement
articles.set([]) // Écraser la valeur manuellement (local override)
resource() gère l'annulation automatique des requêtes obsolètes (race condition), les états intermédiaires (isLoading, error), et le rechargement manuel. toSignal() est plus simple mais sans ces features.
Hydration incrémentale avec @defer
L'hydration incrémentale (stable en Angular 19) combine le rendu SSR et la directive @defer : les parties de page sont d'abord rendues en HTML statique côté serveur, puis hydratées progressivement côté client selon des déclencheurs.
// app.config.ts — activer l'hydration incrémentale
import { provideClientHydration, withIncrementalHydration } from '@angular/platform-browser';
export const appConfig: ApplicationConfig = {
providers: [
provideClientHydration(withIncrementalHydration())
]
};
<!-- Utilisation dans les templates -->
<!-- Zone principale : hydratée immédiatement -->
<app-header />
<app-hero />
<!-- Section commentaires : hydratée quand visible -->
@defer (hydrate on viewport) {
<app-comments />
} @loading {
<div class="skeleton">Chargement des commentaires...</div>
}
<!-- Sidebar : hydratée au hover -->
@defer (hydrate on hover) {
<app-sidebar-recommendations />
}
<!-- Zone footer : hydratée quand idle -->
@defer (hydrate on idle) {
<app-footer-heavy />
}
Déclencheurs d'hydration disponibles
| Déclencheur | Description | Cas d'usage |
|---|---|---|
hydrate on viewport | Visible dans le viewport | Contenu below-the-fold |
hydrate on interaction | Click ou keydown | Widgets interactifs |
hydrate on hover | Survol de la souris | Menus, tooltips |
hydrate on idle | Navigateur inactif (requestIdleCallback) | Footer, widgets non critiques |
hydrate when condition | Signal/expression truthy | Logique conditionnelle |
hydrate never | Jamais hydraté (HTML statique pur) | Contenu entièrement statique |
HMR des styles sans rechargement
Angular 19 améliore le Hot Module Replacement : les modifications de styles CSS/SCSS sont appliquées instantanément sans rechargement de page. L'état de l'application est préservé.
# HMR des styles activé par défaut en ng serve
ng serve
# Pour désactiver
ng serve --no-hmr
- Styles globaux (
styles.scss) : mis à jour sans rechargement - Styles de composants : mis à jour sans rechargement
- Templates HTML : rechargement complet (pas encore HMR)
Effect cleanup et afterRenderEffect
Angular 19 introduit deux améliorations majeures pour la gestion des effets.
1. Effect cleanup : une fonction de nettoyage exécutée avant chaque ré-exécution de l'effet ou à la destruction du composant.
effect((onCleanup) => {
const ws = new WebSocket(this.url());
onCleanup(() => ws.close()); // appelé avant la prochaine exécution
});
2. afterRenderEffect() : un effet qui s'exécute après chaque rendu, uniquement côté client. Parfait pour les mesures DOM ou les animations.
import { afterRenderEffect } from '@angular/core';
export class ChartComponent {
constructor() {
afterRenderEffect(() => {
// Accès sûr au DOM après le rendu
this.drawChart();
});
}
}
Standalone par défaut dans le CLI
À partir d'Angular 19, tous les schematics CLI génèrent du code standalone par défaut.
# Nouveau projet : pas de AppModule
ng new my-app
# Composant standalone automatiquement
ng generate component header
# → standalone: true sans option supplémentaire
# Directive standalone
ng generate directive highlight
# Pipe standalone
ng generate pipe truncate
--no-standalone pour générer en mode classique.
Migration d'Angular 18 à 19
La migration est automatisée et sans breaking changes majeurs.
1. Mettre à jour les dépendances :
ng update @angular/cli@19 @angular/core@19
2. Vérifier avant de migrer :
ng update @angular/core@19 --dry-run
Prérequis :
- TypeScript 5.5 ou supérieur
- Node.js 18.19+ ou 20.9+
- RxJS 7.4+ (inchangé)
@let, le route render mode et l'HMR sont optionnels. Vous pouvez les adopter progressivement sans toucher au code existant.