> ## Content Index
> Fetch the complete content index at: https://www.sfeir.dev/llms.txt
> Use this file to discover other available public pages before exploring further.

# Angular 22 : le point de non-retour vers une architecture Signals-First
- URL: https://www.sfeir.dev/front/angular-22-le-point-de-non-retour-vers-une-architecture-signals-first/
- Published: 2026-06-15T09:14:36.000Z
- Updated: 2026-06-17T12:10:41.000Z
- Description: Sortie le 03 juin 2026, la version 22 d'Angular marque un tournant décisif : passage à OnPush par défaut, Signal Forms prêts pour la production, instanciation lazy et outils de débogage assistés par l'IA. Que tu sois junior ou senior, découvre tout ce qui change aujourd'hui dans ton code !
- Author: Maxime Dewez
- Tags: Angular 22, Zone.js, Front

## Introduction

L'année dernière, Angular 21 posait les jalons d'un futur sans Zone.js et introduisait les prémices des formulaires réactifs basés sur les Signaux. Angular 22 transforme l'essai. Avec le passage de **`OnPush` par défaut**, la stabilisation des **Signal Forms** et l'intégration des **outils de débogage assistés par l'IA**, cette version marque le point de non-retour vers une architecture "Signals-First".

Table des Matières 

## 🏗️ Core & Change Detection

Le gros morceau de cette version, c'est le changement de philosophie sur la détection de changement. On quitte enfin l'ère du "on vérifie tout, tout le temps" !

### OnPush par défaut et stratégie Eager

Jusqu'ici, sans précision de ta part, un composant utilisait la stratégie `Default` (ou `CheckAlways`). À chaque micro-événement dans l'application, Angular parcourait tout l'arbre. C'était "magique", mais coûteux.

Désormais, tous les **nouveaux composants utilisent** `ChangeDetectionStrategy.OnPush` **par défaut**. L'ancienne stratégie `Default` est renommée `Eager` (avide) pour être plus explicite : elle indique qu'un composant demande à être vérifié de manière proactive, même sans preuve de changement.

Le compilateur Angular a été mis à jour. Si tu ne spécifies rien, c'est `OnPush` qui s'applique. Pour tes anciens composants, le `ng update` injectera explicitement la stratégie `Eager` pour garantir que rien ne casse.

```typescript
// Avant migration
@Component({ selector: 'app-old', template: '...' })

// Après migration vers Angular 22
@Component({ 
  selector: 'app-old', 
  template: '...',
  changeDetection: ChangeDetectionStrategy.Eager // Injecté automatiquement
})
```

**Ta mission post-migration :** Ne plus jamais écrire `Eager` pour un nouveau composant. Considère chaque occurrence de `Eager` dans ton code comme une dette technique à transformer progressivement en composants `OnPush` basés sur les Signaux.

[refactor(core): Add migration to add \`ChangeDetectionStrategy.Eager\` … by JeanMeche · Pull Request #67056 · angular/angular…where applicable In v22, OnPush becomes the default strategy. To maintain the ChangeDetection behavior of exisiting apps, components without an explicit change detectino strategy will get Eager as…![](https://static.ghost.org/v5.0.0/images/link-icon.svg)GitHubangular![](https://storage.ghost.io/c/0b/31/0b31478e-a66a-46f1-8c34-d44388e21192/content/images/thumbnail/67056)](https://github.com/angular/angular/pull/67056?ref=sfeir.dev)

### `ApplicationRef.bootstrap()` s'aligne sur `createComponent()`

Angular propose depuis longtemps deux façons de monter dynamiquement un composant. Le problème ? Ces deux API ne parlaient pas le même langage.

`createComponent()` acceptait un objet de configuration riche : un élément hôte, des directives, des bindings. `ApplicationRef.bootstrap()`, elle, se contentait d'un simple sélecteur CSS. Impossible de lui passer des directives ou des bindings sans contournement.

```typescript
// Avant : limité à un sélecteur CSS
appRef.bootstrap(AppComponent, '#app');

// Après : un objet de configuration complet
appRef.bootstrap(AppComponent, {
  hostElement: document.getElementById('app'),
  directives: [{ type: ThemeDirective, bindings: [inputBinding('theme', () => 'dark')] }],
  bindings: [inputBinding('config', () => ({ env: 'production' }))],
});
```

Là où ça devient vraiment intéressant, c'est quand on monte **plusieurs composants racine sur la même page** (comme sur une application legacy avec plusieurs "îlots" Angular). Chaque composant peut désormais recevoir sa propre configuration via `bindings` et `directives`, là où avant on était obligé de tout déverser dans les providers globaux de l'application :

```typescript
// Widget panier : son propre contexte
appRef.bootstrap(CartWidget, {
  hostElement: document.getElementById('cart'),
  bindings: [inputBinding('apiUrl', () => 'https://api-cart.example.com')],
});

// Widget recherche : totalement isolé, URL différente
appRef.bootstrap(SearchWidget, {
  hostElement: document.getElementById('search'),
  bindings: [inputBinding('apiUrl', () => 'https://api-search.example.com')],
});
```

L'ancienne syntaxe reste valide, aucune migration nécessaire. Seul changement notable : le second paramètre perd son ancien type `any` pour `Element`. Fini les erreurs silencieuses !

[feat(core): bootstrap via \`ApplicationRef\` with config by JeanMeche · Pull Request #67948 · angular/angularThis is to align the shape of the method with createComponent BREAKING CHANGE=The second arguement of appRef.bootstrap does not accept any anymore. Make sure the element you pass is not nullable. f…![](https://static.ghost.org/v5.0.0/images/link-icon.svg)GitHubangular![](https://opengraph.githubassets.com/00ec8f4d3d46e7ff913e49b356e62ae62dde029b0070599eceb8932bd8a88729/angular/angular/pull/67948)](https://github.com/angular/angular/pull/67948?ref=sfeir.dev)

### Directive Composition API : Finies les collisions

Introduite dans [Angular 15](https://www.sfeir.dev/front/angular-15/), la Directive Composition API (`hostDirectives`) permet de composer des comportements en attachant des directives à d'autres directives ou composants. Mais elle butait sur une limite frustrante : si deux composants partageaient la même directive hôte et qu'un troisième tentait de les combiner, Angular plantait avec l'erreur `NG0309: Directive AppearanceDirective matches multiple times on the same element. Directives can only match an element once`.

```typescript
// Une directive de style partagée
@Directive({ selector: '[appAppearance]' })
export class AppearanceDirective {}

// Un bouton qui l'utilise
@Component({
  selector: 'app-button',
  hostDirectives: [AppearanceDirective],
})
export class ButtonComponent {}

// Un item de dropdown qui l'utilise aussi
@Component({
  selector: 'app-dropdown-item',
  hostDirectives: [AppearanceDirective],
})
export class DropdownItemComponent {}

// ❌ Avant Angular 22 : NG0309 — AppearanceDirective matchée deux fois
@Component({
  selector: 'app-sidebar-item',
  hostDirectives: [ButtonComponent, DropdownItemComponent],
})
export class SidebarItemComponent {}

```

Heureusement, Angular 22 résout automatiquement ces collisions selon deux règles claires :

**Règle 1 : Le template gagne toujours.** Si une directive est présente à la fois dans le template et dans les `hostDirectives`, c'est l'instance du template qui l'emporte.

**Règle 2 : Les host directives en double fusionnent.** Si la même directive arrive plusieurs fois uniquement via `hostDirectives`, Angular fusionne intelligemment leurs inputs/outputs en une seule instance.

```typescript
// ✅ Angular 22 : fonctionne, AppearanceDirective est dédupliquée automatiquement
@Component({
  selector: 'app-sidebar-item',
  hostDirectives: [ButtonComponent, DropdownItemComponent],
})
export class SidebarItemComponent {}

```

#### ⚠️ Le cas qui reste bloquant : Les alias contradictoires

Il reste un seul cas de figure où le compilateur lèvera une erreur : si les deux sources exposent la même directive avec des alias contradictoires sur les mêmes propriétés (*inputs* ou *outputs*).

```typescript
// ButtonComponent expose 'variant' sous l'alias 'buttonVariant'
@Component({
  selector: 'app-button',
  hostDirectives: [{
    directive: AppearanceDirective,
    inputs: ['variant: buttonVariant'],
  }],
})
export class ButtonComponent {}

// DropdownItemComponent expose le même input sous 'dropdownVariant'
@Component({
  selector: 'app-dropdown-item',
  hostDirectives: [{
    directive: AppearanceDirective,
    inputs: ['variant: dropdownVariant'],
  }],
})
export class DropdownItemComponent {}

// ❌ Erreur à la compilation ou au runtime :
// Input "variant" from AppearanceDirective is exposed under the following
// conflicting names: "buttonVariant" and "dropdownVariant".
// An input can only be exposed under a single name.
@Component({
  selector: 'app-sidebar-item',
  hostDirectives: [
    { directive: ButtonComponent },
    { directive: DropdownItemComponent },
  ],
})
export class SidebarItemComponent {}

// ✅ Fix : aligner les alias des deux côtés
// inputs: ['variant: appearanceVariant'] dans ButtonComponent ET DropdownItemComponent
```

Angular ne peut pas choisir lequel des deux alias garder sans risquer de casser l'un des composants. Le message d'erreur est explicite : il indique précisément quel input pose problème et sous quels noms contradictoires il est exposé. La correction est simple, aligner les alias au sein de tes deux composants.

[feat(core): de-duplicate host directives by crisbeto · Pull Request #67996 · angular/angularWith host directives we can end up in a situation where the same directive applies multiple times to the same element, potentially with conflicting configurations. The runtime isn&#39;t set up for…![](https://static.ghost.org/v5.0.0/images/link-icon.svg)GitHubangular![](https://opengraph.githubassets.com/a8683c8860c387af057dab5106c2295f0eb6ceaecf30cc0dba77099e104f3be9/angular/angular/pull/67996)](https://github.com/angular/angular/pull/67996?ref=sfeir.dev)

### `injectAsync`: lazy-loader ses services Angular sans quitter le DI

Cette version introduit `injectAsync`, un petit helper ultra-pratique qui vient résoudre un problème vieux comme le monde (ou du moins aussi vieux que le *code-splitting* dans Angular) : comment charger un service lourd **seulement quand on en a besoin**, tout en gardant les bénéfices du système d'injection de dépendances ?

Prenons un exemple concret : une application de gestion de commandes. Sur la page de détail d'une commande, un bouton "Exporter en PDF" permet à l'utilisateur de télécharger sa facture. Pour ça, on a un `InvoiceExportService` qui encapsule une bibliothèque de génération PDF qui est lourde par nature.

Le problème ? Dès qu'on injecte ce service dans le composant, Angular embarque toutes ses dépendances dans le bundle initial. **Chaque utilisateur paie le coût de la bibliothèque PDF, même ceux qui ne cliquent jamais sur "Exporter".**

```typescript
@Component({ ... })
export class OrderDetailComponent {
  // ❌ La lib PDF chargée pour tout le monde, même les utilisateurs en lecture seule
  private readonly invoiceExport = inject(InvoiceExportService);
}
```

Avant Angular 22, contourner ce problème nécessitait d'utiliser `Injector` à la main :

```typescript
@Component({ ... })
export class OrderDetailComponent {
  private readonly injector = inject(Injector);

  async onExportClick() {
    const svc = await import('../invoice-export.service').then(m =>
      this.injector.get(m.InvoiceExportService)
    );
    svc.download(this.order());
  }
}
```

Ça fonctionne, mais c'est du boilerplate répétitif : injecter `Injector`, gérer la Promise, reproduire le pattern dans chaque composant qui en a besoin. Angular 22 encapsule tout ça dans un helper dédié :

```typescript
import { injectAsync } from '@angular/core';

@Component({ ... })
export class OrderDetailComponent {
  private readonly invoiceExport = injectAsync(
    () => import('../invoice-export.service').then(m => m.InvoiceExportService)
  );

  async onExportClick() {
    const svc = await this.invoiceExport();
    svc.download(this.order());
  }
}
```

Sous le capot, Angular **capture automatiquement le contexte d'injection courant**, résout le service via le DI avec toutes ses sous-dépendances, et **met le résultat en cache,** le module n'est importé qu'une seule fois, les appels suivants à `this.invoiceExport()` se feront instantanément.

#### Aller plus loin avec le `prefetch`

Pour parfaire l'expérience de tes utilisateurs, la signature d' `injectAsync` accepte un objet d'options permettant de configurer une stratégie de préchargement :

```typescript
function injectAsync<T>(
  loader: () => Promise<ProviderToken<T>>,
  options?: { prefetch?: PrefetchTrigger }
): () => Promise<T>
```

- `**loader**` : une fonction qui retourne un `Promise<ProviderToken<T>>`. En pratique : un dynamic import suivi d'un `.then(m => m.MonService)`.
- **`options.prefetch`** : une stratégie de pré-chargement optionnelle.

L'option `prefetch` permet de déclencher le chargement en arrière-plan **avant** que l'utilisateur n'interagisse, sans impacter le bundle initial.

```typescript
readonly heavySvc = injectAsync(() =>
    import('./heavy.service').then((m) => m.HeavyService),
  { prefetch: () => onIdle({ timeout: 100 }) }
);
```

Grâce à `onIdle`, Angular profite des moments creux du navigateur pour pré-charger discrètement le module. Résultat : ton bundle initial reste léger, et quand l'utilisateur clique sur "Exporter", le service est déjà prêt !

[feat(core): Add an \`injectAsync\` helper function by JeanMeche · Pull Request #68248 · angular/angularThe commit introduces a new function to assist users who want to lazy load services and use the DI system to create them. Example: import {injectAsync} from &#39;angular/core&#39;; class MyCmp {…![](https://storage.ghost.io/c/0b/31/0b31478e-a66a-46f1-8c34-d44388e21192/content/images/icon/pinned-octocat-093da3e6fa40-176.svg)GitHubangular![](https://storage.ghost.io/c/0b/31/0b31478e-a66a-46f1-8c34-d44388e21192/content/images/thumbnail/68248)](https://github.com/angular/angular/pull/68248?ref=sfeir.dev)

### Resource et gestion des statuts

#### Statuts spéciaux dans les `params` de resource

Jusqu'à présent, le contrôle de l'état d'une resource depuis sa fonction `params` était limité : soit tu retournais une valeur pour déclencher le chargement, soit `undefined` pour la mettre en mode `idle`. Il n'y avait aucun moyen propre pour forcer un état `loading` ou `error` directement depuis les paramètres. Angular 22 comble ce manque avec `ResourceParamsStatus` et de la fonction `chain()`.

#### Le problème

Imagine une resource dont les paramètres dépendent d'une autre resource. Si la resource parente est encore en cours de chargement, que fait-on ? Tu étais condamné à gérer cela manuellement avec des conditions verbeuses dans `params` :

```typescript
// ❌ Avant Angular 22 : Gestion manuelle et verbeuse
const userResource = resource({ loader: () => fetchUser() });

const postsResource = resource({
  params: () => {
    const user = this.userResource.value();
    if (!user) return undefined; // Impossible de distinguer idle de loading
    return { userId: user.id };
  },
  loader: ({ params }) => fetchPosts(params.userId),
});
```

#### La solution : `ResourceParamsStatus` et `chain()`

Désormais, la fonction `params` reçoit un contexte (`ResourceParamsContext`) contenant la méthode `chain`. Cette méthode magique propage automatiquement les états de la ressource parente :

```typescript
import { resource, ResourceParamsStatus } from '@angular/core';

const postsResource = resource({
  params: ({ chain }) => ({
    // chain() propage automatiquement idle, loading et error de userResource
    userId: chain(this.userResource).id,
  }),
  loader: ({ params }) => fetchPosts(params.userId),
});
```

`chain(resource)` retourne la valeur résolue de la resource et **throw automatiquement** le bon `ResourceParamsStatus` si la resource parente est en `idle`, `loading` ou `error`. Plus besoin de conditions `if` à rallonge ! 

Tu peux également associer ces statuts de manière explicite, ou utiliser une `Error` classique, pour basculer instantanément ta ressource dans l'état souhaité :

| Valeur throwée               | État resultant |
| ---------------------------- | -------------- |
| ResourceParamsStatus.IDLE    | idle           |
| ResourceParamsStatus.LOADING | loading        |
| Error                        | error          |

```typescript
const filteredResource = resource({
  params: () => {
    if (!hasPermission()) throw new Error('Accès refusé');
    const query = searchQuery();
    if (!query) throw ResourceParamsStatus.IDLE;    // Resource inactive
    if (isLocked()) throw ResourceParamsStatus.LOADING; // Forcer l'état loading
    return { q: query };
  },
  loader: ({ params }) => search(params),
});
```

[feat(core): add special return statuses for resource params by mmalerba · Pull Request #67084 · angular/angularAllows throwing from the resource&#39;s params function to transition the resource to a status other than resolved. In particular, the following values can be thrown from params: ResourceParamsSta…![](https://static.ghost.org/v5.0.0/images/link-icon.svg)GitHubangular![](https://storage.ghost.io/c/0b/31/0b31478e-a66a-46f1-8c34-d44388e21192/content/images/thumbnail/67084)](https://github.com/angular/angular/pull/67084?ref=sfeir.dev)

### Valeurs synchrones dans les stream resources

Cette amélioration concerne un cas précis : les ressources de type *stream* dans un contexte de rendu côté serveur (SSR).

Avant ce changement, le loader d'une `stream` resource devait obligatoirement retourner une `Promise`. Même si la donnée était immédiatement disponible, la resource passait inévitablement par un état `loading` avant de se résoudre.

Ce détail technique a une conséquence directe sur l'hydratation : 

1. En SSR, Angular pré-génère le DOM côté serveur et le transfère au client
2. Au moment de l'hydratation sur le client, si une ressource bascule en état `loading`, Angular considère que le rendu graphique n'est plus valide.
3. Résultat : **Angular détruit instantanément le DOM hydraté pour le reconstruire de zéro**. Tu perds ainsi le bénéfice du SSR, les écouteurs d'événements et les interactions utilisateur en cours.

```typescript
dataResource = resource({
  stream: async () => {
    // ❌ Même avec une donnée déjà disponible, on passe par async
    // → la resource reste en "loading" le temps de la micro-task
    // → Angular détruit le DOM hydraté
    return Promise.resolve<AppState>({ mode: 'show', label: 'Bonjour' })
      .then(data => signal({ value: data }));
  },
});
```

Angular 22 élargit la signature du loader pour lui permettre d'accepter un `Signal` directement, sans passer par une `Promise` :

```typescript
readonly state = signal<AppState>({ mode: 'show', label: 'Coucou du cache' });

dataResource = resource({
  stream: () => {
    const cached = this.state();
    if (cached) {
      // ✅ Résolution synchrone : la resource est immédiatement en état "resolved"
      // → le DOM hydraté est préservé
      return signal({ value: cached });
    }
    
    // Sinon, retour classique via Promise
    return Promise.resolve<AppState>({ mode: 'show', label: 'Bonjour' })
      .then(data => signal({ value: data }));
  },
});
```

En retournant un `Signal` directement (et non une `Promise<Signal>`), la resource se résout dès le premier tick de l'effect, sans passer par l'état `loading`. Le DOM rendu côté serveur reste intact.

**Signature mise à jour**

```typescript
type ResourceStreamingLoader<T, R> = (param: ResourceLoaderParams<R>) =>
    Signal<ResourceStreamItem<T>>               // Nouveau : résolution synchrone
  | PromiseLike<Signal<ResourceStreamItem<T>>>  // Existant : résolution async
  | undefined;                                  // Nouveau : resource inactive          
```

En pratique, ce changement est surtout pertinent pour les stratégies de cache SSR avec `TransferState`, où la donnée est disponible au moment de l'initialisation. Pour les cas purement client (sans SSR), le comportement existant reste inchangé.

[feat(core): allow synchronous values for stream Resources by JeanMeche · Pull Request #67382 · angular/angularIn order for resources to allow caching in SSR context (eg in the TransferState), resource need to be able to set their value synchronously. If the resource value is not set synchronously, the reso…![](https://storage.ghost.io/c/0b/31/0b31478e-a66a-46f1-8c34-d44388e21192/content/images/icon/pinned-octocat-093da3e6fa40-164.svg)GitHubangular![](https://storage.ghost.io/c/0b/31/0b31478e-a66a-46f1-8c34-d44388e21192/content/images/thumbnail/67382)](https://github.com/angular/angular/pull/67382?ref=sfeir.dev)

### Le cache automatique en SSR avec `transferCacheKey`

Lorsque tu développes une application Angular en Server-Side Rendering (SSR), le serveur exécute ton code, appelle l'API pour récupérer les données, génère le HTML et l'envoie au navigateur.

Le problème lors de l'hydratation, c'est que le client (le navigateur) démarre à son tour et réexécute la ressource. Elle relançait donc exactement la même requête HTTP vers ton API.

Problème réglé avec l'introduction de l'option **`transferCacheKey`** directement au cœur de la configuration de **`resource`** et **`rxResource`**.

```typescript
readonly productId = signal(42);

// String simple
readonly productDetailResource = resource({
  params: () => ({ id: this.productId() }),
  loader: async ({ params }) => (await fetch(`/api/products/${params.id}`)).json(),
  id: 'product-detail-cache-key'
});

// Ou avec une StateKey typée (recommandé pour les TypeScript strict)
readonly PRODUCT_DETAIL_KEY = makeStateKey<ProductDetial>('product-detail-cache-key');

readonly productDetailResourceWithStateKey = resource({
  params: () => ({ id: this.productId() }),
  loader: async ({ params }) => (await fetch(`/api/products/${params.id}`)).json(),
  id: this.PRODUCT_DETAIL_KEY
});
```

[feat(core): add ability to cache resources for SSR by JeanMeche · Pull Request #68226 · angular/angularThis commit adds a transferCacheKey option to enable easy caching for resource/ rxResource. By caching resource data we make sure that resources are not in a loading state during hydration on the c…![](https://storage.ghost.io/c/0b/31/0b31478e-a66a-46f1-8c34-d44388e21192/content/images/icon/pinned-octocat-093da3e6fa40-181.svg)GitHubangular![](https://opengraph.githubassets.com/5b196c1dda1c6e09976cf89e99ed21ef74dd88c988dcf820812e8cbb00d8f7b2/angular/angular/pull/68226)](https://github.com/angular/angular/pull/68226?ref=sfeir.dev)

## 🤖 Intégration IA

Angular 22 ne se contente pas d'optimiser le code existant, il ouvre grand la porte aux outils de développement du futur en intégrant nativement des fonctionnalités pensées pour l'Intelligence Artificielle.

### **Outils de débogage IA : `signal_graph` & `di_graph`**

Diagnostiquer une application Angular en mode développement demande souvent de fouiller les DevTools, d'ajouter des `console.log` et de reconstruire mentalement des graphes de dépendances qui peuvent être très profonds. Deux outils de débogage pensés pour les agents IA sont désormais exploitables par des outils comme Claude via Chrome DevTools MCP.

Ces deux outils sont enregistrés automatiquement au démarrage de la plateforme en mode développement, et dés-enregistrés à sa destruction, sans aucune configuration nécessaire de ta part.

**`angular:signal_graph` cartographier la réactivité d'un composant**

Cet outil expose le graphe des signaux d'un composant donné à partir d'un élément du DOM. Il remonte les effets enregistrés sur l'injecteur du composant, puis parcourt récursivement leurs dépendances pour identifier tous les signaux utilisés dans le template ou dans un `effect()`.

En pratique : face à un composant qui ne se met pas à jour comme attendu, un agent IA peut appeler `angular:signal_graph` avec le sélecteur de l'élément concerné et obtenir instantanément le graphe complet des dépendances réactives, quels signaux sont lus, depuis quels effets, et comment ils sont connectés entre eux.

![](https://storage.ghost.io/c/0b/31/0b31478e-a66a-46f1-8c34-d44388e21192/content/images/2026/05/image-28.png)

Exemple signal graph puis Angular DevTools

[Add DI graph AI debugging tool by dgp1130 · Pull Request #68030 · angular/angularThis is part of an experiment with chrome-devtools-mcp to expose runtime data about framework internal state such as the dependency injection graph to AI agents and see if it improves debuggability…![](https://storage.ghost.io/c/0b/31/0b31478e-a66a-46f1-8c34-d44388e21192/content/images/icon/pinned-octocat-093da3e6fa40-167.svg)GitHubangular![](https://opengraph.githubassets.com/d2a4f411dd3218968b9b3e142fd8f85e1f7ce565f947b7ae91aaa2061e3b0039/angular/angular/pull/68030)](https://github.com/angular/angular/pull/68030?ref=sfeir.dev)

**`angular:di-graph` explorer la hiérarchie d'injection**

Le second outil expose l'intégralité du graphe d'injection de l'application. Il localise les structures internes (`LView`), remonte les hiérarchies d'injecteurs et collecte en parallèle les injecteurs d'environment.

Le résultat : pour un composant donné, un agent IA peut répondre à des questions comme "quel provider résout ce token ?", "à quel niveau de l'arbre ce service est-il fourni ?", ou encore "deux composants partagent-ils bien la même instance ?".

[Add signal graph AI debugging tool by dgp1130 · Pull Request #67985 · angular/angularThis is part of an experiment with chrome-devtools-mcp to expose runtime data about framework internal state such as the signal graph to AI agents and see if it improves debuggability of Angular ap…![](https://static.ghost.org/v5.0.0/images/link-icon.svg)GitHubangular![](https://storage.ghost.io/c/0b/31/0b31478e-a66a-46f1-8c34-d44388e21192/content/images/thumbnail/67985-db6534f1b85e5de7d76f1a22128a4462a755698c82f54ce62ba13c1ada2ed5ff)](https://github.com/angular/angular/pull/67985?ref=sfeir.dev)

Ces deux outils partagent une même philosophie : ils ne sont pas destinés à être appelés manuellement depuis le code applicatif, mais à être consommés par des agents IA qui peuvent interpréter ces graphes et en tirer des diagnostics. Ils sont désactivés en mode production.

### **Exposer son application Angular aux agents IA avec WebMCP**

Le Model Context Protocol (MCP) est un standard émergent qui définit comment un agent IA peut invoquer des outils exposés par une application. Angular 22 intègre **WebMCP** nativement dans son système d'injection via deux nouvelles API expérimentales : `declareExperimentalWebMcpTool` et `provideExperimentalWebMcpTools`.

> Ces APIs portent le préfixe `Experimental` et évolueront avec la spécification WebMCP elle-même.

**`declareExperimentalWebMcpTool`**

Elle enregistre un outil WebMCP lié au cycle de vie de l'injecteur courant. Si l'injecteur est détruit, l'outil se désenregistre automatiquement.

Voici comment transformer un simple `ScoreService` pour qu'un agent IA puisse interagir directement avec lui :

```typescript
import { Injectable, signal, computed, declareExperimentalWebMcpTool, inject } from '@angular/core';

@Injectable({ providedIn: 'root' })
export class ScoreService {
  private readonly _score = signal(0);
  private readonly _history = signal<number[]>([0]);

  readonly score = this._score.asReadonly();
  readonly history = this._history.asReadonly();
  readonly best = computed(() => Math.max(0, ...this._history()));

  constructor() {
    declareExperimentalWebMcpTool({
      name: 'increment_score',
      description: 'Incrémente le score du joueur.',
      inputSchema: {
        type: 'object',
        properties: {
          amount: { type: 'number', description: 'Valeur à ajouter' },
        },
        required: ['amount'],
        additionalProperties: false,
      },
      execute: async ({ amount }) => {
        const svc = inject(ScoreService);
        svc.increment(amount as number);
        return { content: [{ type: 'text', text: `Score : ${svc.score()}` }] };
      },
    });

    declareExperimentalWebMcpTool({
      name: 'reset_score',
      description: 'Remet le score à zéro.',
      inputSchema: { type: 'object', properties: {}, additionalProperties: false },
      execute: async () => {
        const svc = inject(ScoreService);
        svc.reset();
        return { content: [{ type: 'text', text: `Score remis à zéro. Meilleur : ${svc.best()}` }] };
      },
    });
  }

  increment(by: number): void {
    this._score.update(s => s + by);
    this._history.update(h => [...h, this._score()]);
  }

  reset(): void {
    this._score.set(0);
    this._history.update(h => [...h, 0]);
  }
}
```

Deux points importants dans l'`execute` : il tourne dans le contexte d'injection de l'enregistrement, donc `inject()` y est disponible, pas besoin de passer les services en paramètre. Et puisque `ScoreService` est `providedIn: 'root'`, les outils vivent aussi longtemps que l'application.

**`provideExperimentalWebMcpTools`**

C'est la version déclarative, un `EnvironmentProviders` qu'on pose dans le tableau `providers` de l'application ou d'une route. Idéal pour fournir des outils transversaux qui n'appartiennent à aucun service en particulier.

```typescript
// app.config.ts
import { provideExperimentalWebMcpTools } from '@angular/core';

export const appConfig: ApplicationConfig = {
  providers: [
    provideRouter(routes),
    provideExperimentalWebMcpTools([
      {
        name: 'get_app_version',
        description: "Retourne la version de l'application.",
        inputSchema: { type: 'object', properties: {} },
        execute: async () => ({
          content: [{ type: 'text', text: 'v22.0.0' }],
        }),
      },
    ]),
  ],
};
```

0:00 

/0:20 

1× 

Un plugin Chrome de test MCP liste les outils disponibles : get\_app\_version, increment\_score, reset\_score. L'exécution successive des trois outils montre get\_app\_version retourner "v22.0.0", increment\_score met à jour le score en direct dans l'interface, et reset\_score le remet à zéro et affiche le meilleur score.

**Des outils liés aux fonctionnalités de tes routes**

L'intérêt majeur de `provideExperimentalWebMcpTools` : apparaît lorsqu'il est placé dans les providers d'une route spécifique et combiné avec `withExperimentalAutoCleanupInjectors()`. L'outil s'enregistre à l'activation de la route et disparaît dès que l'utilisateur change de page. L'agent IA ne voit que les outils pertinents pour la page courante.

```typescript
// app.routes.ts
export const routes: Routes = [
  {
    path: 'catalog',
    loadComponent: () => import('./catalog/catalog.component'),
    providers: [
      provideExperimentalWebMcpTools([
        {
          name: 'search_products',
          description: 'Recherche des produits par mot-clé dans le catalogue.',
          inputSchema: {
            type: 'object',
            properties: {
              query: { type: 'string', description: 'Terme de recherche' },
            },
            required: ['query'],
          },
          execute: async ({ query }) => {
            const svc = inject(CatalogService);
            const results = svc.search(query as string);
            return { content: [{ type: 'text', text: JSON.stringify(results) }] };
          },
        },
      ]),
    ],
  },
  {
    path: 'cart',
    loadComponent: () => import('./cart/cart.component'),
    providers: [
      provideExperimentalWebMcpTools([
        {
          name: 'checkout',
          description: 'Déclenche le paiement du panier courant.',
          inputSchema: { type: 'object', properties: {} },
          execute: async () => {
            const svc = inject(CartService);
            await svc.checkout();
            return { content: [{ type: 'text', text: 'Commande validée.' }] };
          },
        },
      ]),
    ],
  },
];

// app.config.ts — activer le nettoyage automatique des injectors de routes
provideRouter(routes, withExperimentalAutoCleanupInjectors())
```

- Sur `/catalog`, ton agent IA verra et pourra utiliser l'outil `search_products`
- Sur `/cart`, il verra uniquement l'outil `checkout`.

Les outils entrent et sortent du registre de l'IA de manière totalement transparente, sans aucune ligne de nettoyage manuel de ta part !

[Initial WebMCP Support by dgp1130 · Pull Request #68139 · angular/angularThis PR adds declareWebMcpTool and provideWebMcpTools as new, experimental public APIs. declareWebMcpTool is a mechanism for registering WebMCP tools and tying them to Angular&#39;s Injector lifecy…![](https://storage.ghost.io/c/0b/31/0b31478e-a66a-46f1-8c34-d44388e21192/content/images/icon/pinned-octocat-093da3e6fa40-180.svg)GitHubangular![](https://opengraph.githubassets.com/136b4b91cd074c95c267c19422317f8145b612b4df7ebae6b30e58ae94c004f3/angular/angular/pull/68139)](https://github.com/angular/angular/pull/68139?ref=sfeir.dev)

## 🎨 Template & Defer : La finesse du contrôle

Introduit dans les versions précédentes pour révolutionner le Lazy Loading au niveau du composant, le bloc `@defer` gagne en maturité en s'armant de mécanismes de contrôle beaucoup plus fins pour orchestrer le rendu.

### Le timeout pour le trigger `on idle`

Jusqu'à présent, `@defer (on idle)` attendait sagement que le navigateur soit "au repos" (grâce à `requestIdleCallback`) pour charger le bloc concerné. Mais que se passe-t-il si ton application sollicite le navigateur en continu ? Le chargement du bloc pouvait être retardé indéfiniment. Désormais, tu peux passer un **timeout** optionnel :

```html
  @defer (on idle(2000)) {
    <p class="loaded">Contenu chargé après idle (max 2000 ms)</p>
  } @placeholder {
    <p class="placeholder">En attente d'idle…</p>
  }
```

[Support optional timeout for idle deferred triggers by SkyZeroZx · Pull Request #67190 · angular/angularThis change allows supply a timeout value in the on idle trigger of an Angular @defer block, which translates into passing the timeout option to requestIdleCallback. That way, if the browser doesn’…![](https://static.ghost.org/v5.0.0/images/link-icon.svg)GitHubangular![](https://storage.ghost.io/c/0b/31/0b31478e-a66a-46f1-8c34-d44388e21192/content/images/thumbnail/67190)](https://github.com/angular/angular/pull/67190?ref=sfeir.dev)

### Personnalisation via `IdleService`

Si le comportement natif basé sur `requestIdleCallback` convient à la majorité des projets, tu peux avoir besoin d'un contrôle total sur ces timings : environnements de tests unitaires, gestion fine du rendu côté serveur (SSR), ou stratégies de planification sur mesure.

Angular 22 t'offre cette flexibilité en te permettant de surcharger entièrement ce moteur interne grâce au provider `provideIdleServiceWith()`. L'interface `IdleService` prend désormais en charge le type `IdleRequestOptions`, ce qui te permet de récupérer et d'honorer le paramètre `timeout` défini directement dans ton template HTML.

Voici comment mettre en place ton propre planificateur d’idle en utilisant un fallback basé sur `setTimeout` :

```.typescript
import { Injectable } from '@angular/core';
import { IdleService, IdleRequestOptions } from '@angular/core';

@Injectable({ providedIn: 'root' })
export class CustomIdleService implements IdleService {
  private readonly callbacks = new Map<number, ReturnType<typeof setTimeout>>();
  private nextId = 1;

  requestOnIdle(callback: (deadline?: IdleDeadline) => void, options?: IdleRequestOptions): number {
    const id = this.nextId++;
    
    // On utilise un setTimeout, tout en respectant le timeout hérité du template
    const delay = options?.timeout ?? 50;
    const handle = setTimeout(() => callback(), delay);
    
    this.callbacks.set(id, handle);
    return id;
  }

  cancelOnIdle(id: number): void {
    const handle = this.callbacks.get(id);
    if (handle !== undefined) {
      clearTimeout(handle);
      this.callbacks.delete(id);
    }
  }
}
```

Il ne te reste plus qu'à brancher ton service personnalisé dans la configuration globale de ton application :

```.typescript
// app.config.ts
import { ApplicationConfig } from '@angular/core';
import { provideIdleServiceWith } from '@angular/core';
import { CustomIdleService } from './custom-idle.service';

export const appConfig: ApplicationConfig = {
  providers: [
    provideIdleServiceWith(CustomIdleService)
  ]
};
```

[feat(core): support customization of @defer’s on idle behavior by thePunderWoman · Pull Request #67262 · angular/angularThis commit makes the behavior of on idle in @defer configurable via DI. It defines an IdleService interface that an application can implement and provide to Angular: @Injectable({providedIn: &#39;…![](https://static.ghost.org/v5.0.0/images/link-icon.svg)GitHubangular![](https://storage.ghost.io/c/0b/31/0b31478e-a66a-46f1-8c34-d44388e21192/content/images/thumbnail/67262)](https://github.com/angular/angular/pull/67262?ref=sfeir.dev)

### Des commentaires dans les balises HTML ?

Oui, tu as bien lu ! Le compilateur supporte maintenant les commentaires de type Javascript (`//` ou `/* */`) **à l'intérieur même de la balise ouvrante d'un élément**. 

C'est un confort visuel très appréciable pour documenter des listes d'attributs complexes ou ajouter des rappels techniques directement là où ils font sens :

```html
<button
  // Ce bouton est désactivé pendant la phase de maintenance
  /* FIXME : remplacer par une directive [appPermission] avant la v2.0 */
  [disabled]="isMaintenanceMode"

  /* Doit correspondre au tracking analytics défini dans tracking.service.ts */
  data-track-id="submit-main-cta"

  (click)="onSubmit()"
>
  Valider
</button>
```

Cette nouveauté aligne Angular sur ce que proposent déjà d'autres frameworks modernes comme Svelte. À terme, cela ouvre surtout la porte au support de directives de compilation comme `// @ts-ignore` directement dans tes templates. Un détail, mais qui sera très vite indispensable dans des codebase complexes.

[feat(compiler): Support comments in html element. by JeanMeche · Pull Request #67179 · angular/angularLet’s support comments ! <div // comment 0 /\* comment 1 \*/ attr1=“value1” /\* comment 2 spanning multiple lines \*/ attr2=“value2″ ></div>![](https://static.ghost.org/v5.0.0/images/link-icon.svg)GitHubangular![](https://storage.ghost.io/c/0b/31/0b31478e-a66a-46f1-8c34-d44388e21192/content/images/thumbnail/67179)](https://github.com/angular/angular/pull/67179?ref=sfeir.dev)

### Vérification exhaustive avec `@default never()`

Lorsque tu utilises le bloc `@switch` pour gérer l'affichage selon différents scénarios, comment être sûr à 100 % que tu n'as pas oublié un cas en chemin ? Jusqu'ici, le compilateur d'Angular avait du mal à valider cette exhaustivité dès que la variable était imbriquée dans une union de types complexes.

Angular 22 corrige le tir et nous apporte une sécurité bien plus stricte grâce à la syntaxe `never(expression)` au sein du bloc `@default`. Elle permet d'indiquer explicitement à TypeScript quelle variable globale il doit surveiller pour vérifier que tous les cas ont bien été traités.

```
type HideState = { mode: 'hide' };
type ShowState  = { mode: 'show'; label: string };
type AppState   = HideState | ShowState;

// Côté composant
readonly state = signal<AppState>({ mode: 'show', label: 'Bonjour' });
```

```.html
  @switch (state().mode) {
    @case ('show') {
      <p class="state-show">Mode show — label : {{ $any(state()).label }}</p>
    }
    @case ('hide') {
      <p class="state-hide">Mode hide</p>
    }
    @default {
      <!-- Si un nouveau mode est ajouté sans mettre à jour ce switch,
           le compilateur produira une erreur de type -->
      never(state())
    }
  }
```

Plus besoin de croiser les doigts lors des grosses mises à jour. Si demain tu modifies ton code pour ajouter un troisième état (par exemple un mode `'loading'`) mais que tu oublies de mettre à jour ce template, **le projet refusera de compiler**. C'est l'assurance de ne plus jamais laisser un bug s'échapper par simple oubli !

[feat(core): Allow other expression for exhaustive typechecking by JeanMeche · Pull Request #67748 · angular/angularWhen the switched expression is nested within a union, exhaustive typechecking needs to know which expression to check. This change adds the possibility of specifying the expression to check: @Comp…![](https://static.ghost.org/v5.0.0/images/link-icon.svg)GitHubangular![](https://opengraph.githubassets.com/ee89b05e9a846a356b3269284ed9319a66b300b874eb6f619554f8e8806a5585/angular/angular/pull/67748)](https://github.com/angular/angular/pull/67748?ref=sfeir.dev)

### Language Service : L'IDE qui comprend Angular

Cette version apporte une salve d'améliorations majeures au Language Service qui vont transformer ton quotidien dans VS Code (et tous les éditeurs compatibles avec le protocole LSP). L'outillage d'Angular gagne en précision pour t'éviter des erreurs avant même la phase de compilation.

Deux nouveautés majeures font leur apparition :

- **Les Inlay Hints :** Ton IDE affiche désormais de petits indices contextuels directement dans le template pour t'indiquer les types ou les noms des paramètres attendus.

[feat(language-service): add Angular-specific Inlay Hints (LSP 3.17) by kbrilla · Pull Request #66755 · angular/angularPR Checklist The commit message follows our guidelines Tests for the changes have been added Docs have been added / updated PR Type Feature Refactoring (small infrastructure extraction to s…![](https://static.ghost.org/v5.0.0/images/link-icon.svg)GitHubangular![](https://opengraph.githubassets.com/8f6ccb98d94e07e84d461da0433b36c216fb8759aa9337786c17b4608e1c0407/angular/angular/pull/66755)](https://github.com/angular/angular/pull/66755?ref=sfeir.dev)

- **Le support des Document Symbols :** Il devient beaucoup plus facile de naviguer dans la structure interne de tes templates HTML complexes grâce à une cartographie complète de tes éléments lue par l'IDE.

[feat(language-service): add Document Symbols support for Angular templates by kbrilla · Pull Request #66690 · angular/angularPR: feat(language-service): add Document Symbols support for Angular templates Description Adds comprehensive Document Symbols support for Angular templates, enabling the Outline panel, breadcrumbs…![](https://static.ghost.org/v5.0.0/images/link-icon.svg)GitHubangular![](https://storage.ghost.io/c/0b/31/0b31478e-a66a-46f1-8c34-d44388e21192/content/images/thumbnail/66690)](https://github.com/angular/angular/pull/66690?ref=sfeir.dev)

### La navigation safe affine enfin le type-narrowing

Angular 22 corrige un comportement historique du compilateur de templates concernant l'opérateur de navigation sécurisée `?.`. Jusqu'ici, l'utiliser dans une condition `@if` n'avait aucun impact sur la déduction des types (le *type narrowing*) dans le reste du bloc. Désormais, le compilateur s'aligne sur le comportement standard de TypeScript : dès qu'un `?.` valide la présence d'une donnée, le type est automatiquement affiné et débarrassé de ses valeurs nulles ou indéfinies pour tout le reste du bloc conditionnel.

Attention toutefois si tu manipules des signaux, écrire une condition directement sur le signal, comme `@if (user()?.name)`, ne suffira pas à rassurer le compilateur. Pour TypeScript, un appel de fonction, symbolisé par les parenthèses `()` , n'est pas une référence stable : rien ne garantit qu'il renverra la même valeur deux lignes plus bas.

Pour en profiter, il faut donc associer cette nouveauté à la syntaxe `@let` (disponible depuis Angular 18) afin de figer le résultat du Signal dans une variable locale. Une fois la référence stabilisée, la magie d'Angular 22 opère :

```html
@let user = userSignal();

@if (user?.name) {
  <!-- Ici, user est garanti non-null par le compilateur -->
  <p class="user-name">Utilisateur : {{ user.name }}</p>

  @if (user.address) {
    <!-- user.address est garanti non-null ici -->
    <p class="user-city">Ville : {{ user.address.city }}</p>
  }
} @else {
  <p class="no-user">Aucun utilisateur</p>
}
```

Puisque le compilateur sait désormais que tes variables ne sont plus nulles à l'intérieur de ces blocs, tous tes anciens `?.` ou `??` restés dans le code deviennent obsolètes. Si tu utilises la vérification stricte (`strictTemplates`), cela va lever des erreurs de diagnostic au build. C'est l'occasion idéale pour nettoyer tes templates !

[fix(compiler): allow safe navigation to correctly narrow down nullables by JeanMeche · Pull Request #67959 · angular/angularThe commit updates the TCB for safe navigation expressions to allow for correct narrowing of nullables. This will trigger the nullishCoalescingNotNullable and optionalChainNotNullable diagnostics o…![](https://storage.ghost.io/c/0b/31/0b31478e-a66a-46f1-8c34-d44388e21192/content/images/icon/pinned-octocat-093da3e6fa40-163.svg)GitHubangular![](https://storage.ghost.io/c/0b/31/0b31478e-a66a-46f1-8c34-d44388e21192/content/images/thumbnail/67959)](https://github.com/angular/angular/pull/67959?ref=sfeir.dev)

### L'optional chaining retourne enfin `undefined`

Encore un alignement très sain avec les standards du web ! Jusqu'ici, lorsqu'une chaîne d'optional chaining était interrompue dans un template (par exemple `product()?.details?.price` si la propriété `details` n'existait pas), Angular retournait historiquement la valeur `null`. Ce comportement était un héritage du passé, mais il entrait en contradiction directe avec la spécification JavaScript, où l'opérateur `?.` renvoie nativement `undefined`.

Si tu as des conditions strictes basées sur `null` ou des opérateurs de coalescence (`??`) calibrés spécifiquement sur cette ancienne valeur, ce changement peut impacter tes logiques d'affichage.

Pour t'accompagner dans cette transition sans bloquer ton application, Angular introduit une fonction utilitaire de secours : `$safeNavigationMigration()`. Elle permet d'envelopper ton expression pour forcer temporairement le retour à `null`, le temps de mettre à jour tes composants ou tes vérifications :

```html
<p>
  Prix :
  <code class="price-new">
    {{ product()?.details?.price === undefined ? 'Non défini' : product()?.details?.price }}
  </code>
</p>

<p>
  Prix (migration) :
  <code class="price-migration">
    {{ $safeNavigationMigration(product()?.details?.price) === null ? 'null (ancien)' : product()?.details?.price }}
  </code>
</p>
```

Cette fonction de compatibilité est idéale pour lisser ta migration. Elle te permet de passer à Angular 22 sereinement, puis de nettoyer tes templates petit à petit en adaptant tes vérifications aux standards modernes de JavaScript.

[feat(compiler): Angular expressions with optional chaining returns \`undefined\` by JeanMeche · Pull Request #68084 · angular/angularTo mitigate this breaking change, this behavior can be disabled by wrapping expressions with the $safeNavigationMigration magic function. : $safeNavigationMigration(foo?.bar?.baz) fixes #34385, #3…![](https://storage.ghost.io/c/0b/31/0b31478e-a66a-46f1-8c34-d44388e21192/content/images/icon/pinned-octocat-093da3e6fa40-179.svg)GitHubangular![](https://storage.ghost.io/c/0b/31/0b31478e-a66a-46f1-8c34-d44388e21192/content/images/thumbnail/68084-2)](https://github.com/angular/angular/pull/68084?ref=sfeir.dev)

## 🛠️ Outillage & DX : Le confort de développement avant tout

### Documentation d'Angular directement intégrée au profiler Chrome

Petite amélioration de confort qui va changer le quotidien du débogage de performance. Quand tu analyses une trace dans le panneau **Performance** de Chrome DevTools, les événements Angular apparaissaient comme de simples entrées dans la timeline. Pour comprendre précisément à quoi correspondait chaque métrique, tu devais quitter l'inspecteur pour chercher manuellement la bonne page sur la documentation officielle.

Désormais, Angular injecte **un lien hypertexte direct vers sa documentation** au sein de chaque événement capturé par le profiler :

| Événement                    | Documentation liée                     |
| ---------------------------- | -------------------------------------- |
| ChangeDetectionStart/End     | Guide des performances runtime         |
| ChangeDetectionSyncStart/End | Guide des performances runtime         |
| AfterRenderHooksStart/End    | afterEveryRender et afterNextRender    |
| DeferBlockStateStart/End     | Guide du @defer                        |
| ngOnInit, ngOnDestroy, etc.  | Section dédiée dans le guide lifecycle |

Pour en profiter, tu as besoin de **Chrome 140** minimum et de l'activation du flag expérimental suivant dans ton navigateur : `chrome://flags/#enable-devtools-deep-link-via-extensibility-api`.

[feat(core): enhance profiling with documentation URLs by SkyZeroZx · Pull Request #67942 · angular/angularEnhances the Chrome DevTools performance profiling integration by adding links to relevant Angular documentation for lifecycle hooks and profiler events. Closes #63959 Second attempt at #65606 , wh…![](https://storage.ghost.io/c/0b/31/0b31478e-a66a-46f1-8c34-d44388e21192/content/images/icon/pinned-octocat-093da3e6fa40-165.svg)GitHubangular![](https://opengraph.githubassets.com/a08b3904cd62fbacf12ab669197e49cdaec3ac7a769e1fab06fa06987e54106c/angular/angular/pull/67942)](https://github.com/angular/angular/pull/67942?ref=sfeir.dev)

### Http : L'API `fetch` prend le pouvoir par défaut

Depuis Angular 2, toutes les requêtes gérées par le `HttpClient` passaient historiquement par `XMLHttpRequest` (via `HttpXhrBackend`). Angular 22 bascule le backend par défaut vers l'**API** `fetch` **native** du navigateur. Ce changement était préparé depuis Angular 18 avec `withFetch()`. Désormais cette fonction est dépréciée et peut être supprimée sans effet : `fetch` est actif d'office.

```
// ❌ Avant Angular 22
provideHttpClient(withFetch())

// ✅ Angular 22+ : fetch par défaut, withFetch() devient inutile
provideHttpClient()
```

⚠️ **Breaking change : le suivi de progression des uploads n'est plus supporté par défaut.** L'API `fetch` ne permet pas de suivre la progression d'un upload côté client. Les applications qui utilisaient `reportProgress: true` avec une requête `POST` ou `PUT` contenant un body devront explicitement revenir sur `XHR` via `withXhr()` :

```
provideHttpClient(withXhr())
```

Une migration automatique est incluse dans `ng update`. Elle détecte les appels à `provideHttpClient()` dans les applications existantes et ajoute `withXhr()` pour préserver le comportement actuel, sans intervention manuelle.

[feat(http): Use the Fetch backend by default by JeanMeche · Pull Request #58212 · angular/angularThis commit replaces the HttpXhrBackend with the FetchBackend as the default implementation of the HttpBackend. This introduces a breaking change a the FetchBackend does not support the report prog…![](https://static.ghost.org/v5.0.0/images/link-icon.svg)GitHubangular![](https://storage.ghost.io/c/0b/31/0b31478e-a66a-46f1-8c34-d44388e21192/content/images/thumbnail/58212)](https://github.com/angular/angular/pull/58212?ref=sfeir.dev)

### Scission de `reportProgress` : `reportUploadProgress` & `reportDownloadProgress`

Le passage par défaut à `fetch` a révélé un défaut de conception de l'ancienne option `reportProgress` : elle était binaire alors que les deux backends ont des capacités asymétriques. `fetch` supporte nativement le suivi des téléchargements via `ReadableStream`, mais ne supporte pas le suivi des uploads. Fusionner les deux dans un seul booléen rendait impossible de lever une erreur précise.

Pour clarifier la situation, l'option globale `reportProgress` est officiellement **dépréciée**. Elle est remplacée par deux options distinctes et explicites sur `HttpRequest`:

```
// Avant
this.http.post('/upload', file, { reportProgress: true, observe: 'events' })

// Après
this.http.post('/upload', file, { reportUploadProgress: true, observe: 'events' })
this.http.get('/file', { reportDownloadProgress: true, observe: 'events' })
```

**Comportement en fonction du backend choisi :**

- `reportDownloadProgress` fonctionne parfaitement, que tu utilises le moteur `fetch` ou `XHR`.
- `reportUploadProgress`, en revanche, lève une erreur d'exécution ( `RuntimeError: FETCH_UPLOAD_PROGRESS_NOT_SUPPORTED`) si ton application tourne sous `FetchBackend`. Pour l'utiliser, le retour à `withXhr()` est obligatoire.

Les deux nouvelles options coexistent avec l'ancienne pendant la période de dépréciation, ce qui laisse le temps de migrer sans rien casser.

[refactor(http): Add \`reportUploadProgress\` & \`reportDownloadProgre… by JeanMeche · Pull Request #68495 · angular/angular…ss\` options In order to raise an error on upload progress on the FetchBackend, we split reportProgress into 2 respective properties. DEPRECATION: The reportProgress option is deprecated please use…![](https://storage.ghost.io/c/0b/31/0b31478e-a66a-46f1-8c34-d44388e21192/content/images/icon/pinned-octocat-093da3e6fa40-185.svg)GitHubangular![](https://storage.ghost.io/c/0b/31/0b31478e-a66a-46f1-8c34-d44388e21192/content/images/thumbnail/68495)](https://github.com/angular/angular/pull/68495?ref=sfeir.dev)

## 🚀 Migrations

### TypeScript 6.0 devient le strict minimum

Angular 22 abandonne le support de TypeScript 5.9pour exiger au minimum **TypeScript 6.0**. Ce n'est pas une surprise, Angular suit de près les versions TypeScript et abandonne régulièrement les anciennes à chaque mineure. 

Ce qui mérite attention ici, c'est que TypeScript 6.0 est une version majeure avec quelques changements de comportement par défaut :

- Le mode `strict` est activé par défaut.
- La propriété `module` bascule par défaut sur `esnext`
- La cible de compilation `target` passe à `es2025`

Si ton `tsconfig.json` ne définit pas ces options explicitement, la mise à jour TypeScript seule peut changer le comportement de ton projet.

`ng update` se charge de la mise à jour de la dépendance automatiquement mais un coup d'œil au `tsconfig.json` avant de lancer la migration reste donc vivement conseillé !

[feat(core): drop support for TypeScript 5.9 by crisbeto · Pull Request #68009 · angular/angularDrops support for TypeScript 5.9\. BREAKING CHANGE: TypeScript versions older than 6.0 are no longer supported.![](https://static.ghost.org/v5.0.0/images/link-icon.svg)GitHubangular![](https://storage.ghost.io/c/0b/31/0b31478e-a66a-46f1-8c34-d44388e21192/content/images/thumbnail/68009)](https://github.com/angular/angular/pull/68009?ref=sfeir.dev)

fix(migrations): prevent trailing comma syntax errors after removing NgStyle  
[https://github.com/angular/angular/commit/730684b9ce8335b91ff224422fb12b7eafeaec1d](https://github.com/angular/angular/commit/730684b9ce8335b91ff224422fb12b7eafeaec1d?ref=sfeir.dev)  
[https://github.com/angular/angular/pull/67714](https://github.com/angular/angular/pull/67714?ref=sfeir.dev)

[fix(migrations): prevent trailing comma syntax errors after removing NgStyle by aparzi · Pull Request #67714 · angular/angularThis fixes an issue where when removing NgStyle from the imports array of a component, an extra trailing comma would be left behind if it was the last element in that component\`. PR Checklist Pleas…![](https://static.ghost.org/v5.0.0/images/link-icon.svg)GitHubangular![](https://opengraph.githubassets.com/4a6ee0630a121e8e978129f6e0ac2fc9ff553e6df130da9d8724ad38cc6fcfa5/angular/angular/pull/67714)](https://github.com/angular/angular/pull/67714?ref=sfeir.dev)

fix(migrations): inject migration not work in multi-project workspace with option path

[https://github.com/angular/angular/commit/a73b4b7c30ae943966ad6deecf5a284cddb1f3fd](https://github.com/angular/angular/commit/a73b4b7c30ae943966ad6deecf5a284cddb1f3fd?ref=sfeir.dev)  
[https://github.com/angular/angular/issues/66074](https://github.com/angular/angular/issues/66074?ref=sfeir.dev)

[Inject migration does not work in a multi-project workspace · Issue #66074 · angular/angularWhich @angular/\* package(s) are the source of the bug? core Is this a regression? No Description In multi-project workspace, the inject migration doesn’t work when targeting one of the projects due…![](https://storage.ghost.io/c/0b/31/0b31478e-a66a-46f1-8c34-d44388e21192/content/images/icon/pinned-octocat-093da3e6fa40-73a943d2-84c5-4451-8d03-8840c3b7e1a3.svg)GitHubkaiosales![](https://opengraph.githubassets.com/57e6e4a0b8ab27dfbf071f317ab13d8b5893c611c3b26fe3515e11dcb0874d88/angular/angular/issues/66074)](https://github.com/angular/angular/issues/66074?ref=sfeir.dev)

### Protection de build : `strictTemplates` forcé à `false`

Angular pousse activement vers un typage strict des templates pour détecter les erreurs dès la phase de compilation. Cependant, activer brusquement le flag `strictTemplates` sur un projet existant lors d'une mise à jour majeure peut générer des centaines d'erreurs de build d'un coup, bloquant ainsi le travail des équipes.

`ng update` ajoute désormais `"strictTemplates": false` dans le `tsconfig.json` des projets existants. Sans ce flag explicite, un `ng update` pourrait déclencher une avalanche d'erreurs de compilation sur du code qui fonctionnait avant. Cela permet de figer temporairement ton comportement actuel pour migrer sereinement vers Angular 22 et l'équipe peut activer `strictTemplates: true` quand elle le décide, à son rythme.

[feat(migrations): add strictTemplates to tsconfig during ng update by aparzi · Pull Request #67884 · angular/angularAdd the strictTemplates option to tsconfig.json with a default value of false PR Checklist Please check if your PR fulfills the following requirements: The commit message follows our guidelines:…![](https://static.ghost.org/v5.0.0/images/link-icon.svg)GitHubangular![](https://opengraph.githubassets.com/613d21a66c594d40d5298683f11ce0f35956cd72e52b6cc53faf49e66d732423/angular/angular/pull/67884)](https://github.com/angular/angular/pull/67884?ref=sfeir.dev)

### `currentSnapshot` requis dans `CanMatchFn`

Le troisième paramètre, `currentSnapshot` (de type `PartialMatchRouteSnapshot`), devient **obligatoire**. Une migration automatique intégrée `can-match-snapshot-required` détecte les classes qui implémentent `CanMatch` avec deux paramètres et ajoute le troisième automatiquement :

```
// Avant
class MyGuard implements CanMatch {
  canMatch(route: Route, segments: UrlSegment[]) { ... }
}

// Après (migration)
class MyGuard implements CanMatch {
  canMatch(route: Route, segments: UrlSegment[], currentSnapshot: PartialMatchRouteSnapshot) { ... }
}
```

[fix(router): make currentSnapshot required in CanMatchFn by atscott · Pull Request #67452 · angular/angularit was only optional to avoid a breaking change in a minor BREAKING CHANGE: The currentSnapshot parameter in CanMatchFn and the canMatch method of the CanMatch interface is now required. While this…![](https://storage.ghost.io/c/0b/31/0b31478e-a66a-46f1-8c34-d44388e21192/content/images/icon/pinned-octocat-093da3e6fa40-d54e7028-2870-4ba8-b63c-d8bcd93dd3a1.svg)GitHubangular![](https://opengraph.githubassets.com/161275e60e926ca900e0b120ecab01e5ba684934736df7aafc534f3d27dc52d3/angular/angular/pull/67452)](https://github.com/angular/angular/pull/67452?ref=sfeir.dev)

### Désactivation automatique des options pour opérateurs obsolètes

Comment on l'a vu plus haut, le compilateur sait désormais que tes variables ne sont plus nulles à l'intérieur de ces blocs conditionnels. Par conséquent, **tous tes anciens `?.` (optional chaining) ou `??` (nullish coalescing) restés dans le code de tes templates deviennent instantanément obsolètes et redondants.**

Pour éviter de bloquer les projets lors de la mise à jour, la commande `ng update` va automatiquement désactiver temporairement deux options de vérification dans ton fichier `tsconfig.json` 

- `nullishCoalescingNotNullable`
- `optionalChainNotNullable`

Ton application continuera de compiler normalement, te laissant le temps de nettoyer tes templates pour supprimer ces opérateurs devenus inutiles.

[feat(migrations): Disabling nullishCoalescingNotNullable & optionalChainNotNullable on ng update by aparzi · Pull Request #68080 · angular/angularRelated to #67959 disabling two diagnostics errors by ng update: nullishCoalescingNotNullable optionalChainNotNullable PR Checklist Please check if your PR fulfills the following requirements:…![](https://storage.ghost.io/c/0b/31/0b31478e-a66a-46f1-8c34-d44388e21192/content/images/icon/pinned-octocat-093da3e6fa40-170.svg)GitHubangular![](https://storage.ghost.io/c/0b/31/0b31478e-a66a-46f1-8c34-d44388e21192/content/images/thumbnail/68080)](https://github.com/angular/angular/pull/68080?ref=sfeir.dev)

### Migration Modèles + Outputs : Fin des collisions

Pour rappel, l'API `model()` génère implicitement deux éléments : la propriété de lecture (exemple: `foo` ) pour la valeur et une sortie d'événement `fooChange` pour l'événement. Si un composant déclarait déjà un `output()` nommé `fooChange` en parallèle, les deux entraient en collision.

La migration va remplacer intelligemment ton `model()` conflictuel par une combinaison hybride d'un `input()` standard couplé à un `linkedSignal()`, reproduisant le même comportement fonctionnel mais sans aucun conflit de nommage :

```
// Avant — collision : model() génère fooChange, output fooChange existe déjà
foo = model.required<number>();
fooChange = output<number>();

// Après (migration)
fooInput = input.required<number>({alias: 'foo'});
foo = linkedSignal(this.fooInput);
fooChange = output<string>();
```

[feat(migrations): model + output migrations by aparzi · Pull Request #67349 · angular/angularbecomes input + linkedSignal fixes #67340 PR Checklist Please check if your PR fulfills the following requirements: The commit message follows our guidelines: https://github.com/angular/angular/b…![](https://storage.ghost.io/c/0b/31/0b31478e-a66a-46f1-8c34-d44388e21192/content/images/icon/pinned-octocat-093da3e6fa40-174.svg)GitHubangular![](https://opengraph.githubassets.com/849065d4752682ef4bef14dcdd7faf475bf07d15e9400d2937194777b6a63021/angular/angular/pull/67349)](https://github.com/angular/angular/pull/67349?ref=sfeir.dev)

### L'hydratation incrémentale s'active par défaut

Dernière excellente surprise pour clore ce chapitre sur la migration : l'**hydratation incrémentale** devient le comportement par défaut d'Angular . Intégrée nativement au sein de la fonction `provideClientHydration()`, cette fonctionnalité majeure te permet de charger et d'hydrater ton code HTML côté client de manière progressive, uniquement lorsque l'utilisateur interagit avec une zone de la page . Un atout inestimable pour les performances de tes applications à fort trafic ! Elle est accompagnée de `withNoIncrementalHydration()` pour désactivier l'hydratation incrémentale.

[feat(platform-browser): make incremental hydration default behavior by thePunderWoman · Pull Request #68092 · angular/angularThis PR automatically enables incremental hydration by default in provideClientHydration(). It introduces a new withNoIncrementalHydration() feature for opting out, implements developmental conflic…![](https://storage.ghost.io/c/0b/31/0b31478e-a66a-46f1-8c34-d44388e21192/content/images/icon/pinned-octocat-093da3e6fa40-171.svg)GitHubangular![](https://storage.ghost.io/c/0b/31/0b31478e-a66a-46f1-8c34-d44388e21192/content/images/thumbnail/68092)](https://github.com/angular/angular/pull/68092?ref=sfeir.dev)

## 📝 Formulaires : La consécration des Signal Forms

### Passage en API publique

Introduites comme expérimentales dans Angular 21, les **Signal Forms** sont **officiellement stables en Angular 22**. Toutes les APIs sous `@angular/forms/signals` abandonnent le tag `@experimental` pour le précieux sésame `@publicApi`.

Pour rappel, **Signal Forms** est une API de formulaires construite entièrement sur les signaux. À la différence des formulaires réactifs classiques (`FormControl`, `FormGroup`), elle définit un schéma de données typé, des règles de validation déclaratives, et une synchronisation automatique avec l'UI.

[feat(forms): graduate signal forms APIs to public API by alxhub · Pull Request #68581 · angular/angularReplaced @experimental tags with @publicApi 22.0 across all Signal Forms APIs under packages/forms/signals to mark them as ready for general use in v22.![](https://storage.ghost.io/c/0b/31/0b31478e-a66a-46f1-8c34-d44388e21192/content/images/icon/pinned-octocat-093da3e6fa40-183.svg)GitHubangular![](https://storage.ghost.io/c/0b/31/0b31478e-a66a-46f1-8c34-d44388e21192/content/images/thumbnail/68581)](https://github.com/angular/angular/pull/68581?ref=sfeir.dev)

### L'instanciation "Lazy" des champs

Quand Angular initialisait un formulaire Signal Forms, il construit un **arbre de nœuds** (`FieldNode`), un nœud par champ. Jusqu'ici, cet arbre était entièrement construit au démarrage, même pour les champs qui n'ont aucune règle de validation, aucun debounce, aucune logique.

Sur des formulaires massifs (tableaux dynamiques, fiches techniques complexes), cette surcharge de travail initiale pouvait ralentir l'affichage du composant et consommer de la mémoire inutilement.

Angular 22 corrige cela et désormais, l'arbre du formulaire ne génère ses nœuds et ses sous-champs **qu'au moment précis où l'application ou le template tente d'y accéder**.

```markdown
Formulaire
├── name (required, minLength) ← logique → instancié immédiatement
├── bio (aucune règle)         ← pas de logique → instancié à la demande
├── address
│   ├── street (aucune règle) ← pas de logique → instancié à la demande
│   └── zip (pattern)         ← logique → instancié immédiatement
└── preferences (aucune règle) ← pas de logique → instancié à la demande
```

Le nœud de tes préférences ne sera créé en mémoire que si ton utilisateur clique sur l'onglet "Paramètres" qui affiche cette partie du formulaire. Une optimisation invisible mais massive pour la fluidité de tes applications !

[perf(forms): lazily instantiate signal form fields by alxhub · Pull Request #67344 · angular/angularCurrently, Signal Forms eagerly instantiates all nodes in the form tree because childrenMap iterates over the value and creates a FieldNode for every property. This ensures validation side-effects…![](https://static.ghost.org/v5.0.0/images/link-icon.svg)GitHubangular![](https://storage.ghost.io/c/0b/31/0b31478e-a66a-46f1-8c34-d44388e21192/content/images/thumbnail/67344)](https://github.com/angular/angular/pull/67344?ref=sfeir.dev)

### `touched` : une propriété qui n'était pas faite pour le two-way binding

Dans les Signal Forms, les contrôles de formulaire exposaient une propriété `touched` utilisable en two-way binding `[(touched)]`. Ce choix de conception permettait à un composant enfant de "dé-toucher" un champ, c'est-à-dire remettre `touched` à `false`.

Angular 22 sépare les rôles en deux :

- `[touched]` (input) : reçoit l'état depuis le parent
- `(touch)` (output) : **signale** que l'utilisateur vient d'interagir avec le champ

```typescript
// ❌ Avant : lecture ET écriture possible
@Component({ selector: 'my-input', ... })
class MyInput implements FormValueControl<string> {
  value = model('');
  touched = model(false); // two-way binding involontaire

  touchIt() {
    this.touched.set(true); // pouvait aussi faire .set(false) ← problème
  }
}

// ✅ Après
@Component({ selector: 'my-input', ... })
class MyInput implements FormValueControl<string> {
  readonly value = model('');
  readonly touched = input(false);    // reçoit l'état depuis le parent
  readonly touch = output<void>();    // signale l'interaction, jamais l'absence

  touchIt() {
    this.touch.emit(); // émet un événement, ne touche plus à l'état directement
  }
}
```

[fix(forms): split the \`touched\` model into an input and \`touch\` output by leonsenft · Pull Request #67100 · angular/angularThe touched property was never meant to support two-way binding; a control should not be able to dictate that a field is no longer touched. The touched input represents the touched state of the fi…![](https://static.ghost.org/v5.0.0/images/link-icon.svg)GitHubangular![](https://storage.ghost.io/c/0b/31/0b31478e-a66a-46f1-8c34-d44388e21192/content/images/thumbnail/67100)](https://github.com/angular/angular/pull/67100?ref=sfeir.dev)

### L'option `'blur'` débarque sur la règle de debounce

La fonction `debounce()` de Signal Forms accepte désormais `'blur'` comme configuration, en plus d'une durée en millisecondes.

Quand tu passes `'blur'`, la valeur saisie par l'utilisateur ne sera poussée vers ton modèle de données **que lorsque le champ perdra le focus**. C'est idéal pour éviter les recalculs intempestifs sur les validations lourdes :

```typescrpti
readonly #formSchema = schema<FormModel>((path) => {
  required(path.name);
  debounce(path.name, 400);
  
  required(path.email);
  debounce(path.email, 'blur');
});
```

[feat(forms): add ‘blur’ option to debounce rule by leonsenft · Pull Request #67418 · angular/angularExpands the debounce rule configuration to accept &#39;blur&#39;. When this option is provided, the rule will delay model synchronization until the field loses focus (is touched). This introduces a…![](https://static.ghost.org/v5.0.0/images/link-icon.svg)GitHubangular![](https://storage.ghost.io/c/0b/31/0b31478e-a66a-46f1-8c34-d44388e21192/content/images/thumbnail/67418)](https://github.com/angular/angular/pull/67418?ref=sfeir.dev)

### Validation asynchrone

Deux grosses nouveautés viennent perfectionner les contrôles asynchrones.

#### Le Debounce intégré sur `validateAsync` et `validateHttp`

Plus besoin de ruser pour éviter de spammer ton serveur à chaque touche pressée. Tu peux désormais configurer un paramètre `debounce` directement au cœur de ton validateur HTTP :

```typescript
validateHttp(path.email, {
  request: ({value}) => `/api/check?email=${value()}`,
  onSuccess: (available: boolean) => (available ? undefined : {kind: 'email-taken'}),
  onError: () => null,
  debounce: 500
});
```

[feat(forms): add debounce option to validateAsync and validateHttp by alxhub · Pull Request #67813 · angular/angularThis adds support for a debounce option to the validateAsync and validateHttp functions. This allows developers to debounce the triggering of async validators to improve performance. A DebounceTime…![](https://static.ghost.org/v5.0.0/images/link-icon.svg)GitHubangular![](https://storage.ghost.io/c/0b/31/0b31478e-a66a-46f1-8c34-d44388e21192/content/images/thumbnail/67813)](https://github.com/angular/angular/pull/67813?ref=sfeir.dev)

#### `reloadValidation()` : forcer une revalidation

Certains validateurs asynchrone dépendent de données extérieures au champ (un état serveur, un autre champ du formulaire…), tu peux désormais appeler `reloadValidation()` sur un `FieldState`. Cela va forcer l'exécution de tous les validateurs asynchrones du nœud et de ses enfants, comme si l'utilisateur venait de modifier la valeur.

```
form = form({ email: field<string>() });

this.form().reloadValidation();
```

[feat(forms): add \`reloadValidation\` to Signal Forms to manually trigger async validation by alxhub · Pull Request #67360 · angular/angularThis commit introduces a formal mechanism to manually re-trigger asynchronous validations in Signal Forms, addressing #66994\. It exposes a reloadValidation method on the FieldState interface that r…![](https://static.ghost.org/v5.0.0/images/link-icon.svg)GitHubangular![](https://storage.ghost.io/c/0b/31/0b31478e-a66a-46f1-8c34-d44388e21192/content/images/thumbnail/67360)](https://github.com/angular/angular/pull/67360?ref=sfeir.dev)

### Récupérer une erreur spécifique avec `FieldState.getError()`

Pour afficher tes messages d'erreur de manière élégante, tu devais souvent fouiller tout l'objet des erreurs renvoyé par le champ. `FieldState` expose désormais `getError(kind: string)`. 

Elle renvoie directement la première erreur du type demandé (ou `undefined`), et met à jour automatiquement dès que l'état change.

```html
@let emailError = form.email().getError('email-taken');

@if (emailError) {
  <p class="error">Cet email est déjà utilisé.</p>
}
```

[feat(forms): add FieldState.getError() by alxhub · Pull Request #67811 · angular/angularAdded a getError(kind: string) method to FieldState that returns the first validation error of a given kind, or undefined if no such error exists. This method is reactive and will re-evaluate when…![](https://static.ghost.org/v5.0.0/images/link-icon.svg)GitHubangular![](https://storage.ghost.io/c/0b/31/0b31478e-a66a-46f1-8c34-d44388e21192/content/images/thumbnail/67811)](https://github.com/angular/angular/pull/67811?ref=sfeir.dev)

### Lier un `number|null` à `<input type="text">`

`<input type="number">` offre une UX souvent indésirable : flèches de défilement, comportements surprenants selon les navigateurs. [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/input/number?ref=sfeir.dev#using%5Fnumber%5Finputs) recommande recommande d'ailleurs plutôt l'usage de `<input type="text" inputmode="numeric">`.

Signal Forms ne savait pas convertir `number|null` depuis/vers une chaîne de caractères sans un intermédiaire. Angular 22 gère ce binding automatiquement. Dès que ton `[formField]` est lié à un modèle numérique, les règles de conversion suivantes sont appliquées :

- chaîne vide → `null`
- chaîne numérique valide → `number`
- chaîne non numérique → erreur `parseError`

```html
<input type="text" inputmode="numeric" [formField]="form.amount" />
```

[feat(forms): support binding \`number|null\` to \`<input type=“text”>\` by alxhub · Pull Request #67268 · angular/angular&lt;input type=&quot;number&quot;&gt; often does not provide the desired user experience when editing numbers in a form. MDN even describes how text inputs should be used in many cases instead, via…![](https://static.ghost.org/v5.0.0/images/link-icon.svg)GitHubangular![](https://storage.ghost.io/c/0b/31/0b31478e-a66a-46f1-8c34-d44388e21192/content/images/thumbnail/67268)](https://github.com/angular/angular/pull/67268?ref=sfeir.dev)

### `FormValueControl<T>` : Adieu l'enfer de `ControlValueAccessor`

Pour concevoir un composant de formulaire personnalisé réutilisable compatible avec `ngModel` ou `formControl`, il fallait jusqu'ici implémenter l'interface `ControlValueAccessor` et ses verbeuses méthodes `writeValue()`, `registerOnChange()`, `registerOnTouched()` etc. Une syntaxe complexe, très éloignée de la philosophie des Signaux.

**`FormValueControl<T>` : une interface minimaliste**

Angular 22 introduit `FormValueControl<T>`. Un composant qui l'implémente expose simplement un `model()` nommé `value` :

```typescript
import { Component, model } from '@angular/core';
import { FormValueControl } from '@angular/forms/signals';

@Component({
  selector: 'fancy-input',
  template: `
    <textarea [value]="value()" (input)="value.set($event.target.value)"></textarea>
  `,
})
export class FancyInput implements FormValueControl<string> {
  value = model<string>('');
}
```

Ce composant est immédiatement compatible avec **toutes** les syntaxes de formulaires Angular :

```html
<!-- Template forms -->
<fancy-input [(ngModel)]="myValue" />
  
<!-- Reactive forms -->
<fancy-input [formControl]="myControl" />
<fancy-input formControlName="email" />

<!-- Signal Forms -->
<fancy-input [formField]="state.fields.email" />
```

[Signal Forms: support FormUiControl in Template & Reactive Forms by alxhub · Pull Request #67267 · angular/angularIn this PR, we implement support in template &amp; reactive forms for the new style of Signal Forms control: @Component({ selector: &#39;fancy-input&#39;, template: \` &lt;textarea \[value\]=&…![](https://storage.ghost.io/c/0b/31/0b31478e-a66a-46f1-8c34-d44388e21192/content/images/icon/pinned-octocat-093da3e6fa40-016c9237-2cf0-4e5f-8d16-7089d40eab9a.svg)GitHubangular![](https://storage.ghost.io/c/0b/31/0b31478e-a66a-46f1-8c34-d44388e21192/content/images/thumbnail/67267-5f055303-d37c-422e-9e76-73773db29acb)](https://github.com/angular/angular/pull/67267?ref=sfeir.dev)

### Typage strict et nouvelles règles de validation

Les règles de validation `min` et `max` n'acceptent plus les chaînes de caractères (`string`) et requièrent uniquement types numérique `number` ou des objets de type `Date`.

Pour les dates, de nouvelles règles explicites `minDate` et `maxDate` font leur apparition et se chargent de positionner les attributs HTML `min`/`max` correctement formatés sur les éléments `<input type="date">` et `<input type="month">`.

```typescript
required(path.birthday);
minDate(path.birthday, new Date('1998-05-11'));
maxDate(path.birthday, new Date());
```

[\[Signal Forms\] Update \`min\` and \`max\` to operate on \`number\` and \`Date\` values by leonsenft · Pull Request #68001 · angular/angularRemove string support from min and max validation rules. This is no longer necessary since FormField can bind a numeric field to a text based control. Add minDate and maxDate validation rules. Thes…![](https://storage.ghost.io/c/0b/31/0b31478e-a66a-46f1-8c34-d44388e21192/content/images/icon/pinned-octocat-093da3e6fa40-184.svg)GitHubangular![](https://storage.ghost.io/c/0b/31/0b31478e-a66a-46f1-8c34-d44388e21192/content/images/thumbnail/68001)](https://github.com/angular/angular/pull/68001?ref=sfeir.dev)

### `ngNoCva` : désactiver le fallback `ControlValueAccessor`

Si un composant implémente `FormValueControl` mais qu'il hérite d'un `ControlValueAccessor` (via une bibliothèque tierce ou un composant parent), Angular pourrait se tromper et brancher le mauvais protocole. La directive `ngNoCva` désactive ce fallback explicitement :

```html
<input ngNoCva [field]="myWonderfulField">
```

[Signal Forms: support FormUiControl in Template & Reactive Forms by alxhub · Pull Request #67267 · angular/angularIn this PR, we implement support in template &amp; reactive forms for the new style of Signal Forms control: @Component({ selector: &#39;fancy-input&#39;, template: \` &lt;textarea \[value\]=&…![](https://static.ghost.org/v5.0.0/images/link-icon.svg)GitHubangular![](https://opengraph.githubassets.com/50f150e21f43b8c0bd3b34fead34c3933ffb6f3d8405d8978e4d5f927641e08f/angular/angular/pull/67267)](https://github.com/angular/angular/pull/67267?ref=sfeir.dev)

### En vrac : Les petites corrections qui font du bien

**`[formRoot]` sans gestion de soumission.** La directive `[formRoot]` levait une erreur si le formulaire n'avait pas de configuration `submit`. Elle se contente maintenant de poser `novalidate` et d'intercepter le `submit` natif, sans appeler quoi que ce soit.

[fix(forms): allow FormRoot to be used without submission options by leonsenft · Pull Request #67727 · angular/angularThe \[formRoot\] directive will no longer call submit() if the bound form doesn&#39;t define its own submission options. This allows the directive to be used solely for the default behavior it provid…![](https://static.ghost.org/v5.0.0/images/link-icon.svg)GitHubangular![](https://storage.ghost.io/c/0b/31/0b31478e-a66a-46f1-8c34-d44388e21192/content/images/thumbnail/67727)](https://github.com/angular/angular/pull/67727?ref=sfeir.dev)

**Support des `NG_VALIDATORS` en mode CVA.** Les validateurs Angular classiques (`NG_VALIDATORS`) posés en externe sur un composant `FormValueControl` sont désormais pris en charge nativement dans le système `parseErrors` de Signal Forms. Les deux mondes coexistent sans configuration supplémentaire.

[Support NG\_VALIDATORS in CVA mode by alxhub · Pull Request #67943 · angular/angularfix(forms): use controlValue in NgControl for CVA interop feat(forms): shim legacy NG\_VALIDATORS into parseErrors for CVA mode![](https://storage.ghost.io/c/0b/31/0b31478e-a66a-46f1-8c34-d44388e21192/content/images/icon/pinned-octocat-093da3e6fa40-169.svg)GitHubangular![](https://storage.ghost.io/c/0b/31/0b31478e-a66a-46f1-8c34-d44388e21192/content/images/thumbnail/67943)](https://github.com/angular/angular/pull/67943?ref=sfeir.dev)

### Signals : Une réactivité sous contrôle

L'écosystème des Signaux continue de s'enrichir pour combler les derniers cas d'usage qui nécessitaient encore l'utilisation de RxJS. Angular 22 franchit un cap majeur avec la gestion native du temps.

### L'utilitaire `debounced()` : Temporise tes Signaux sans RxJS

Jusqu'à présent, lorsqu'on voulait retarder la prise en compte d'un changement sur un Signal (par exemple, attendre que l'utilisateur arrête de taper dans un champ de recherche avant de lancer une requête lourde), le framework n'offrait pas de solution native.

Tu étais obligé de faire un aller-retour fastidieux vers RxJS en convertissant ton Signal en Observable, en lui appliquant l'opérateur `debounceTime`, pour ensuite le ré-envelopper dans un Signal :

```typescript
// ❌ Avant Angular 22
readonly searchQuery = signal('');
searchQuery$ = toObservable(this.searchQuery).pipe(debounceTime(1000));

debouncedSearchQuery = toSignal(this.searchQuery$);
```

Avec l'introduction de la fonction utilitaire **`debounced()`**, plus besoin d'importer la moindre ligne de RxJS pour temporiser tes données!

Cette fonction prend ton Signal d'origine, lui applique un délai d'attente (en millisecondes), et génère une version temporisée. Sous le capot, le résultat est **représenté sous la forme d'une `Resource`**, ce qui te permet de suivre facilement son état (si le debounce est en cours, etc.) :

```typescript
// ✅ Avec Angular 22
readonly searchQuery = signal('');
debouncedSearchQuery = debounced(this.searchQuery, 1000);

```

[feat(core): allow debouncing signals by mmalerba · Pull Request #67044 · angular/angularAdds a utility debounced to create a debounced version of a signal, represented as a Resource. The resource&#39;s value contained the debounced value of the signal, while its status (resolved, load…![](https://static.ghost.org/v5.0.0/images/link-icon.svg)GitHubangular![](https://opengraph.githubassets.com/d87396d40463567292604112b0a67e46f19d046486dbcf6402722dad01da90a7/angular/angular/pull/67044)](https://github.com/angular/angular/pull/67044?ref=sfeir.dev)

## Tests : Un outillage de plus en plus affûté

### `TestBed.getLastFixture()` : Accède instantanément à ton composant

Lors de l'écriture de tests d'intégration complexes, il arrive fréquemment que tu doives instancier un composant, puis récupérer sa "fixture" (l'objet qui permet de manipuler le composant et de déclencher la détection de changement) au sein de fonctions utilitaires ou de sous-blocs `it`.

Jusqu'ici, tu étais obligé de déclarer une variable globale au bloc `describe` et de lui assigner manuellement la valeur à chaque création :

```typescript
// ❌ Avant Angular 22
describe('MyComponent', () => {
  let component: MyComponent;
  let fixture: ComponentFixture<MyComponent>;

  beforeEach(() => {
    fixture = TestBed.createComponent(MyComponent);
    component = fixture.componentInstance;
  });

  function clickSubmitButton() {
    const button = fixture.nativeElement.querySelector('button');
    button.click();
    fixture.detectChanges();
  }
  
  it('should submit successfully', () => {
    clickSubmitButton();
    // ...
  });
});
```

La nouvelle méthode `TestBed.getLastFixture()` te permet de **récupérer instantanément la fixture du composant le plus récemment créé** au sein de ton contexte de test, sans avoir à la stocker dans une variable locale :

```typescript
// ✅ Avec Angular 22
describe('MyComponent', () => {
  beforeEach(() => {
    TestBed.createComponent(MyComponent);
  });

  function clickSubmitButton() {
    const fixture = TestBed.getLastFixture(); 
    const button = fixture.nativeElement.querySelector('button');
    button.click();
    fixture.detectChanges();
  }
  
  it('should submit successfully', () => {
    clickSubmitButton();
    // ...
  });
});
```

[feat(core): TestBed.getFixture -> TestBed.getLastFixture and update i… by atscott · Pull Request #67483 · angular/angular…mplementation TestBed.getFixture is now TestBed.getLastFixture and returns the last fixture created rather than throwing if there are more than 1.![](https://static.ghost.org/v5.0.0/images/link-icon.svg)GitHubangular![](https://storage.ghost.io/c/0b/31/0b31478e-a66a-46f1-8c34-d44388e21192/content/images/thumbnail/67483)](https://github.com/angular/angular/pull/67483?ref=sfeir.dev)

## 🛣️ Router : Navigation fine et bindings avancés

Le Routeur d'Angular continue de s'affiner dans cette version, notamment pour offrir un contrôle total sur ce qui s'affiche dans la barre d'adresse du navigateur et pour sécuriser le transfert de données vers tes composants.

### L'input `browserUrl` : Personnalise l'affichage de ta barre d'adresse

C'est une excellente nouveauté pour l'expérience utilisateur (UX). Jusqu'ici, la directive `routerLink` gérait de manière indissociable le composant à charger et l'URL à afficher dans le navigateur.

La nouvelle propriété **`browserUrl`** te permet de séparer complètement la route interne du rendu visuel :

```html
<a routerLink="/catalog/products/detail/42" [browserUrl]="/my-wonderfull-product">
  Voir le produit
</a>
```

Lorsque l'utilisateur clique sur ce lien, Angular va bien déclencher la navigation interne vers la route technique `/catalog/products/detail/42` pour charger le bon composant, mais il va instantanément mettre à jour l'URL du navigateur pour afficher `/my-wonderfull-product`.

[feat(router): adds browserUrl input support to router links by SkyZeroZx · Pull Request #67228 · angular/angularSecond attempt at #66840 , which was previously reverted.![](https://static.ghost.org/v5.0.0/images/link-icon.svg)GitHubangular![](https://storage.ghost.io/c/0b/31/0b31478e-a66a-46f1-8c34-d44388e21192/content/images/thumbnail/67228)](https://github.com/angular/angular/pull/67228?ref=sfeir.dev)

### `withComponentInputBinding()` s'arme d'un paramètre d'options

Depuis Angular 16, la fonction `withComponentInputBinding()` permet d'associer automatiquement les paramètres d'une route (les paramètres de chemin, les *query params* ou les données du bloc `data`) directement à des `input()` dans tes composants. Angular 22 fait évoluer cette API en lui ajoutant un objet de configuration optionnel pour te donner un contrôle sur ces liaisons.

L'option `queryParams` te permet de spécifier si les paramètres de requête (ce qui se trouve après le `?` dans l'URL) doivent être injectés comme des entrées du composant.

- **`true` (par défaut) :** Le routeur lie automatiquement toutes les variables de l'URL à tes inputs.
- **`false` :** Angular ignore totalement les *query params*, te permettant d'isoler uniquement les paramètres de chemin stricts.

De son côté, l'option `unmatchedInputBehavior` dicte la manière dont le framework doit réagir lorsqu'un composant possède un `input()`, mais que la route active n'envoie aucun paramètre correspondant. Tu as le choix entre deux comportements très précis :

- **`'alwaysUndefined'` (par défaut) :** Angular injecte systématiquement `undefined` à l'entrée du composant si le routeur n'a rien trouvé à lui transmettre.
- **`'undefinedIfStale'` :** Angular va injecter `undefined` uniquement si cette entrée était présente et alimentée par le routeur lors de la route *précédente*. Si l'entrée n'a jamais eu vocation à être définie par le routeur, Angular n'y touche pas, préservant ainsi sa valeur par défaut ou son état interne initial.

Voici comment intégrer proprement ces options dans ton fichier de configuration globale :

```typecript
// app.config.ts
import { ApplicationConfig } from '@angular/core';
import { provideRouter, withComponentInputBinding } from '@angular/router';
import { routes } from './app.routes';

export const appConfig: ApplicationConfig = {
  providers: [
    provideRouter(
      routes,
      withComponentInputBinding({
        queryParams: false, // Désactive la liaison automatique des query params (?search=...)
        unmatchedInputBehavior: 'undefinedIfStale' // Protège les valeurs par défaut de tes composants
      })
    )
  ]
};
```

Et voici l'impact concret au cœur de ton composant de destination :

```typescript
@Component({ ... })
export class ProductDetailComponent {
  // Ce paramètre provient du chemin strict 'product/:id' -> Toujours mappé correctement
  id = input.required<string>(); 

  // Un état interne propre au composant (sans rapport avec le routeur).
  // Grâce à 'undefinedIfStale', Angular ne viendra pas l'écraser avec un 'undefined'
  // lors du chargement initial de la route !
  isMenuOpen = signal(false); 
}
```

[feat(router): Add options optional parameter for with component input binding by JordanW9232 · Pull Request #67408 · angular/angularPR Checklist Please check if your PR fulfills the following requirements: The commit message follows our guidelines: https://github.com/angular/angular/blob/main/contributing-docs/commit-message-…![](https://static.ghost.org/v5.0.0/images/link-icon.svg)GitHubangular![](https://storage.ghost.io/c/0b/31/0b31478e-a66a-46f1-8c34-d44388e21192/content/images/thumbnail/67408)](https://github.com/angular/angular/pull/67408?ref=sfeir.dev)

[feat(router): add unmatchedInputBehavior option to componentInputBinding by atscott · Pull Request #68373 · angular/angularIntroduce a new configuration option unmatchedInputBehavior to the componentInputBinding feature. This option allows users to configure the behavior when a component input is not matched by any key…![](https://storage.ghost.io/c/0b/31/0b31478e-a66a-46f1-8c34-d44388e21192/content/images/icon/pinned-octocat-093da3e6fa40-186.svg)GitHubangular![](https://storage.ghost.io/c/0b/31/0b31478e-a66a-46f1-8c34-d44388e21192/content/images/thumbnail/68373)](https://github.com/angular/angular/pull/68373?ref=sfeir.dev)

## 🏁 En bref : On migre ou on attend ?

Bilan des courses : Angular 22 ne fait pas semblant. Ce n'est pas une simple mise à jour de confort, c'est le moment où toutes les briques de ces dernières années s'emboîtent enfin. Avec **`OnPush` par défaut**, les **Signal Forms stables** et l'utilitaire `**injectAsync**`, le message est clair : le framework est désormais *Signals-First*. Grâce à un `ng update` bien rodé qui fige l'ancien comportement, tu peux sauter le pas sereinement et nettoyer ton code à ton propre rythme.

#### 🔮 Et après ?

La suite s'annonce tout aussi intéressante. Pendant que l'intégration native de **WebMCP** prépare discrètement ton architecture actuelle à devenir un ensemble d'outils typés directement pilotables par des agents IA , Angular 22 prouve qu'il a atteint la maturité nécessaire pour de la grosse production.

Le terrain de jeu est propre, moderne et ultra-performant. Alors, tu lances le `ng update` quand ?