Appearance
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 ?
Phase initiale :
- Le serveur envoie le HTML statique
- L'utilisateur voit immédiatement le contenu
- La page n'est pas encore interactive
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.
Sur 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 initialResourceStatus.Loading
: pendant le chargement initialResourceStatus.Error
: en cas d'erreurResourceStatus.Resolved
: données chargées avec succèsResourceStatus.Reloading
: pendant un rechargementResourceStatus.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 promesseisLoading()
: indique si la ressource est en cours de chargementerror()
: contient l'erreur si la promesse est rejetéestatus()
: indique l'état de la ressourcereload()
: 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 promesseisLoading()
: indique si la ressource est en cours de chargementerror()
: contient l'erreur si la promesse est rejetéestatus()
: 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 lesinput()
basés sur les signaux - Conversion des
@Output
vers la nouvelle fonctionoutput()
- 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
PendingTasks.run()
: nouvelle méthode simplifiée pour gérer les tâches asynchronespendingUntilEvent()
: 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 :
- Utilisez
PendingTasks
pour les opérations asynchrones critiques - Activez
withEventReplay()
pour une meilleure expérience utilisateur - Utilisez
pendingUntilEvent()
pour les observables importants - 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 buildRenderMode.Server
: génère la page côté serveur à chaque requêteRenderMode.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 :
- Ajouter la configuration nécessaire
- Créer le fichier
app.routes.server.ts
- 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 :
- Installez le support du rendu hybride :
bash
ng add @angular/ssr --server-routing
- Créez le fichier de configuration serveur :
bash
ng generate @angular/core:server-routes
- 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 actuelREQUEST_CONTEXT
: passage de métadonnées personnaliséesRESPONSE_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