Angular 21.2 : support TypeScript 6, instanceof dans les templates, arrow functions, ChangeDetectionStrategy.Eager et ResourceSnapshot détaillés.
Support officiel de TypeScript 6
Angular 21.2 est la première version du framework à supporter officiellement TypeScript 6. Ce n'est pas anecdotique : les diagnostics de templates reposent directement sur le système de types de TypeScript, et chaque version majeure du compilateur améliore la précision des erreurs remontées dans vos templates HTML.
Ce que TypeScript 6 change pour Angular
TypeScript 6 introduit notamment des améliorations du control flow analysis (analyse du flux de contrôle) et du narrowing. Angular profite immédiatement de ces gains dans ses templates :
| Domaine | Avant TypeScript 6 | Avec TypeScript 6 |
|---|---|---|
| Narrowing dans @if | Partiel, inférence limitée | Complet, instanceof et typeof propagés |
| Erreurs de template | Parfois tardives ou incomplètes | Détection précoce, messages précis |
| Autocomplétion IDE | Suggestions génériques | Suggestions typées selon le contexte |
| Types conditionnels | Évaluation parfois incorrecte | Évaluation stricte et cohérente |
Vérifier la compatibilité dans votre projet
Avant de mettre à jour, vérifiez que vos librairies tierces sont compatibles TypeScript 6. La commande ng update s'en charge automatiquement, mais une vérification manuelle évite les surprises :
# Vérifier la version TypeScript installée
npx tsc --version
# Mettre à jour TypeScript 6 dans le projet
npm install typescript@6 --save-dev
# Vérifier la compatibilité déclarée par Angular
cat node_modules/@angular/core/package.json | grep peerDependencies -A 10
@angular/language-service@21.2.x) pour bénéficier de l'autocomplétion améliorée dans les templates.
instanceof dans les templates
C'est probablement la fonctionnalité la plus attendue de cette version. L'opérateur instanceof est désormais utilisable directement dans les templates Angular, à l'intérieur des blocs @if, @switch et dans les expressions de binding.
Avant / après : éliminer le boilerplate de type guard
Avant Angular 21.2, brancher l'affichage selon le type d'un objet nécessitait une méthode getter dans le composant :
// AVANT Angular 21.2 — getter obligatoire pour le narrowing
@Component({
template: `
@if (isAdmin) {
<admin-panel [permissions]="adminUser.adminPermissions" />
}
`
})
export class UserCardComponent {
@Input() user!: BasicUser | AdminUser | SuperAdmin;
// Méthode boilerplate juste pour le type check
get isAdmin(): boolean {
return this.user instanceof AdminUser;
}
// Cast manuel non typé
get adminUser(): AdminUser {
return this.user as AdminUser;
}
}
Avec Angular 21.2, le narrowing se fait directement dans le template. TypeScript comprend que dans le bloc @if, user est bien de type AdminUser :
// APRÈS Angular 21.2 — instanceof natif + narrowing dans le template
@Component({
template: `
@if (user instanceof AdminUser) {
<!-- TypeScript sait que user est AdminUser ici -->
<admin-panel [permissions]="user.adminPermissions" />
} @else if (user instanceof SuperAdmin) {
<super-admin-panel [role]="user.superRole" />
} @else {
<user-card [profile]="user.profile" />
}
`
})
export class UserCardComponent {
@Input() user!: BasicUser | AdminUser | SuperAdmin;
// Zéro méthode helper nécessaire
}
Narrowing dans les bindings de propriétés
Le narrowing fonctionne aussi dans les expressions de propriétés, pas uniquement dans les blocs de contrôle :
// Narrowing appliqué aux réponses HTTP — Angular 21.2
@Component({
template: `
@if (response instanceof HttpErrorResponse) {
<!-- .status et .error disponibles sans cast -->
<div class="alert alert-danger">
Erreur {{ response.status }} : {{ response.error?.message }}
</div>
} @else {
<pre>{{ response.body | json }}</pre>
}
@if (event instanceof KeyboardEvent) {
<span>Touche pressée : {{ event.key }}</span>
}
`
})
export class ResponseDisplayComponent {
@Input() response!: HttpResponse<unknown> | HttpErrorResponse;
@Input() event!: Event | KeyboardEvent | MouseEvent;
}
instanceof fonctionne avec les classes concrètes uniquement. Les interfaces TypeScript n'existent pas au runtime. Pour les types structurels, utilisez des discriminants (type === 'admin') ou des type guards dans le composant. instanceof les complète, il ne les remplace pas.
Cas d'usage concrets en entreprise
- Affichage conditionnel selon le type d'erreur HTTP (
HttpErrorResponse vs HttpResponse) - Composants polymorphes recevant une union de types en
@Input() - Gestion des événements DOM typés (
Event | KeyboardEvent | DragEvent) - Réponses d'API discriminées par classe (pattern Value Object en DDD)
- Réduction du boilerplate dans les smart components à state management complexe
Arrow functions dans les templates
Angular 21.2 autorise les arrow functions directement dans les expressions de template. Cette fonctionnalité simplifie radicalement les interactions avec les Signals immuables (tableaux, objets) et les callbacks d'événements.
Le problème classique avec les Signals immuables
Ajouter un élément à un Signal de tableau nécessitait une méthode dans le composant, même pour une opération triviale :
// AVANT Angular 21.2 — méthode obligatoire dans le composant
@Component({
template: `
<button (click)="addItem()">Ajouter</button>
@for (item of items(); track item.id) {
<li>{{ item.label }}</li>
}
`
})
export class TodoListComponent {
items = signal<TodoItem[]>([]);
newLabel = signal('');
// Méthode boilerplate — juste pour wrapper signal.update()
addItem() {
this.items.update(list => [
...list,
{ id: Date.now(), label: this.newLabel() }
]);
}
}
Avec Angular 21.2, l'arrow function s'écrit directement dans le template :
// APRÈS Angular 21.2 — arrow function dans le template
@Component({
template: `
<button (click)="items.update(list => [...list, { id: Date.now(), label: newLabel() }])">
Ajouter
</button>
@for (item of items(); track item.id) {
<li>
{{ item.label }}
<button (click)="items.update(list => list.filter(i => i.id !== item.id))">
Supprimer
</button>
</li>
}
`
})
export class TodoListComponent {
items = signal<TodoItem[]>([]);
newLabel = signal('');
// Plus aucune méthode helper nécessaire
}
Arrow functions dans les pipes et les directives
Les arrow functions fonctionnent dans tous les contextes d'expression Angular, y compris pour les filtres et tris inline :
// Filtrage et tri inline — Angular 21.2
@Component({
template: `
<!-- Filtrer les utilisateurs actifs -->
@for (user of users().filter(u => u.isActive); track user.id) {
<user-card [user]="user" />
}
<!-- Trier par ordre croissant -->
@for (item of items().sort((a, b) => a.order - b.order); track item.id) {
<li>{{ item.title }}</li>
}
<!-- Transformer la valeur d'un select -->
<select (change)="selectedId.set(+$event.target.value)">
@for (opt of options(); track opt.value) {
<option [value]="opt.value">{{ opt.label }}</option>
}
</select>
`
})
export class DataListComponent {
users = signal<User[]>([]);
items = signal<Item[]>([]);
options = signal<SelectOption[]>([]);
selectedId = signal<number | null>(null);
}
computed() dans le composant pour la testabilité et la lisibilité. Le template doit rester déclaratif.
ChangeDetectionStrategy.Eager
Angular 21.2 introduit ChangeDetectionStrategy.Eager : un nouvel alias fonctionnellement identique à Default, mais porteur d'une signification sémantique forte qui annonce la direction future du framework.
Pourquoi un alias sémantique ?
Le nom Default est trompeur en 2026 : il sous-entend que c'est le comportement normal, alors que dans l'écosystème Signals, OnPush tend à devenir la norme. Eager exprime explicitement l'intention : "je veux la détection de changement agressive".
// Ancienne syntaxe — toujours valide, mais sémantique ambiguë
@Component({
selector: 'app-live-ticker',
changeDetection: ChangeDetectionStrategy.Default,
template: `<span>{{ price | currency }}</span>`
})
export class LiveTickerComponent {
@Input() price = 0;
}
// Nouvelle syntaxe Angular 21.2 — intention explicite
@Component({
selector: 'app-live-ticker',
changeDetection: ChangeDetectionStrategy.Eager,
template: `<span>{{ price | currency }}</span>`
})
export class LiveTickerComponent {
@Input() price = 0;
// Eager = "ce composant DOIT être vérifié à chaque cycle"
// Cas d'usage : WebSocket, animations, données temps réel
}
Vision future : Eager comme marqueur d'intention
L'équipe Angular a documenté la feuille de route : dans une version future (Angular 22 ou 23), OnPush deviendra le mode par défaut pour les composants Standalone. À ce moment-là :
| Stratégie | Aujourd'hui (v21.2) | Future version |
|---|---|---|
Default |
Détection agressive (comportement par défaut) | Alias gardé pour compatibilité |
Eager |
Alias de Default, même comportement | Marqueur explicite : "je veux la détection agressive" |
OnPush |
Opt-in, recommandé pour les performances | Mode par défaut pour les composants Standalone |
ChangeDetectionStrategy.Default que vous souhaitez garder en détection agressive intentionnellement (WebSocket, temps réel), remplacez-les par Eager dès maintenant. Cela documente l'intention et facilitera les migrations futures.
ResourceSnapshot : Signals asynchrones composés
Angular 21.2 introduit ResourceSnapshot, une abstraction qui combine plusieurs ressources asynchrones Signal-based en un seul objet snapshot unifié. C'est la réponse d'Angular au problème classique de la gestion d'état multi-sources.
Le problème : jongler avec plusieurs états loading/error
Une page tableau de bord qui charge utilisateur, statistiques et notifications doit gérer trois états indépendants. Sans ResourceSnapshot, le template devient verbeux :
// AVANT — gestion manuelle de 3 ressources asynchrones
@Component({
template: `
@if (userResource.isLoading()) {
<spinner />
} @else if (userResource.error()) {
<error-block [err]="userResource.error()" />
} @else {
<user-header [user]="userResource.value()" />
}
@if (statsResource.isLoading() || notifResource.isLoading()) {
<skeleton-loader />
} @else {
<stats-panel [data]="statsResource.value()" />
<notif-list [items]="notifResource.value()" />
}
`
})
export class DashboardComponent {
userResource = resource({ loader: () => this.userSvc.getUser() });
statsResource = resource({ loader: () => this.statsSvc.getStats() });
notifResource = resource({ loader: () => this.notifSvc.getAll() });
}
Avec resourceSnapshot(), les trois sources sont composées en un seul objet :
// APRÈS Angular 21.2 — ResourceSnapshot pour une gestion unifiée
import { resourceSnapshot } from '@angular/core';
@Component({
template: `
@let snap = dashboard();
@if (snap.isLoading) {
<full-page-loader />
} @else if (snap.hasError) {
<error-page [errors]="snap.errors" />
} @else {
<user-header [user]="snap.values.user" />
<stats-panel [data]="snap.values.stats" />
<notif-list [items]="snap.values.notifs" />
}
`
})
export class DashboardComponent {
private userSvc = inject(UserService);
private statsSvc = inject(StatsService);
private notifSvc = inject(NotifService);
private userRes = resource({ loader: () => this.userSvc.getUser() });
private statsRes = resource({ loader: () => this.statsSvc.getStats() });
private notifRes = resource({ loader: () => this.notifSvc.getAll() });
// Composition : un seul snapshot pour les 3 sources
dashboard = resourceSnapshot({
user: this.userRes,
stats: this.statsRes,
notifs: this.notifRes,
});
}
Interface du snapshot
L'objet retourné expose une interface unifiée et typée :
// Interface ResourceSnapshot (simplifiée)
interface ResourceSnapshotResult<T extends Record<string, Resource<unknown>>> {
isLoading: boolean; // true si AU MOINS UNE ressource charge
hasError: boolean; // true si AU MOINS UNE ressource a une erreur
errors: unknown[]; // toutes les erreurs actives
isResolved: boolean; // true si TOUTES les ressources ont une valeur
values: { [K in keyof T]: ResourceValue<T[K]> }; // typé depuis les génériques
}
resourceSnapshot déclenche toutes les ressources en parallèle (équivalent de Promise.all). Pour des ressources dépendantes (ex : stats nécessitent l'ID utilisateur), utilisez les dépendances réactives de resource() via un Signal computed — le snapshot s'adapte automatiquement.
Signal Forms : améliorations v21.2
Les Signal Forms, introduites expérimentalement dans Angular 20, reçoivent plusieurs correctifs majeurs dans v21.2 : meilleur support de null, gestion du focus améliorée, et interopérabilité avec les Reactive Forms classiques.
Support correct de null pour les champs numériques
Un bug classique des formulaires : un <input type="number"> vide renvoyait une chaîne vide "" au lieu de null. Angular 21.2 corrige ce comportement dans Signal Forms :
// Signal Forms v21.2 — null handling correct pour les inputs numériques
import { signalForm, field } from '@angular/forms/signal';
@Component({
template: `
<form [signalForm]="invoiceForm">
<input type="number" [field]="invoiceForm.controls.amount" />
<input type="number" [field]="invoiceForm.controls.quantity" />
<p>
Total :
{{ computedTotal() !== null ? (computedTotal() | currency) : 'Remplissez tous les champs' }}
</p>
</form>
`
})
export class InvoiceFormComponent {
invoiceForm = signalForm({
amount: field<number | null>(null), // null = vide, distinct de 0
quantity: field<number | null>(null),
});
computedTotal = computed(() => {
const amount = this.invoiceForm.controls.amount.value();
const quantity = this.invoiceForm.controls.quantity.value();
if (amount === null || quantity === null) return null;
return amount * quantity; // 0 * 5 = 0 est une valeur valide
});
}
Gestion du focus et accessibilité
Signal Forms 21.2 place automatiquement le focus sur le premier champ invalide lors d'une soumission échouée, conformément aux WCAG 2.1 :
// Focus automatique sur le premier champ invalide — Signal Forms 21.2
@Component({
template: `
<form [signalForm]="loginForm" (submit)="onSubmit()">
<input id="email-field" type="email" [field]="loginForm.controls.email"
[attr.aria-invalid]="loginForm.controls.email.invalid()" />
<input id="password-field" type="password" [field]="loginForm.controls.password"
[attr.aria-invalid]="loginForm.controls.password.invalid()" />
@if (loginForm.controls.email.error()) {
<p class="text-danger" role="alert">Email invalide</p>
}
<button type="submit">Connexion</button>
</form>
`
})
export class LoginFormComponent {
loginForm = signalForm({
email: field('', { validators: [Validators.email, Validators.required] }),
password: field('', { validators: [Validators.minLength(8)] }),
});
onSubmit() {
if (this.loginForm.invalid()) {
// Angular 21.2 : focus automatique sur le premier champ invalide
this.loginForm.focusFirstInvalidField();
return;
}
// Traitement...
}
}
Bridge Reactive Forms → Signal Forms
Pour les migrations progressives, un adaptateur permet d'utiliser un FormControl existant comme source d'un Signal Form field :
// Migration progressive — bridge Reactive Forms vers Signal Forms
import { fromReactiveControl } from '@angular/forms/signal';
@Component({
template: `
<!-- Ancien composant (Reactive Forms) -->
<input [formControl]="legacyControl" />
<!-- Nouveau composant Signal Forms — même FormControl en source -->
<new-input-component [field]="bridgedField" />
<!-- Les deux sont synchronisés automatiquement -->
<p>Valeur actuelle : {{ bridgedField.value() }}</p>
`
})
export class MigrationExampleComponent {
legacyControl = new FormControl('initial value');
bridgedField = fromReactiveControl(this.legacyControl);
}
@angular/forms/signal, l'API est stable pour les nouveaux projets mais toujours marquée @developer-preview dans Angular 21.2. Attendez la stabilisation avant de migrer de grandes apps Reactive Forms.
Migrer vers Angular 21.2
La migration de 21.x vers 21.2 est non-breaking. Tous les changements sont additifs et rétrocompatibles. Voici la procédure recommandée.
Commandes de mise à jour
# Mettre à jour Angular CLI et Core
ng update @angular/cli@21.2 @angular/core@21.2
# Mettre à jour les librairies officielles
ng update @angular/material@21.2 @angular/cdk@21.2
# Vérifier les migrations automatiques appliquées
git diff
# Vérifier TypeScript 6 est bien installé
npx tsc --version
# Attendu : Version 6.x.x
Checklist post-migration
- Vérifier que
typescriptest à 6.x danspackage.json - Lancer
ng build --configuration productionsans erreurs - Remplacer les
ChangeDetectionStrategy.Defaultintentionnels parEager - Identifier les type guards candidats au refactoring avec
instanceofen template - Relancer la suite de tests (
ng test) — aucun changement cassant attendu - Vérifier les logs console au démarrage pour les avertissements de dépréciation
Repérer les candidats à instanceof en template
# Chercher les type guards dans les composants (candidats à déplacer en template)
grep -rn "instanceof\|is[A-Z][a-z]\|as[A-Z][a-z]" src/app --include="*.ts" | grep -v "spec.ts"
# Exemple de résultats typiques :
# user-card.component.ts: isAdmin(): boolean { return this.user instanceof AdminUser; }
# event-log.component.ts: isKeyboard(e: Event): e is KeyboardEvent { return e instanceof KeyboardEvent; }
Récapitulatif et bilan stratégique
Angular 21.2 est une version mineure, mais chaque fonctionnalité livre un signal clair sur la direction du framework pour 2026-2027.
| Fonctionnalité | Bénéfice immédiat | Signal stratégique |
|---|---|---|
| TypeScript 6 | Meilleurs diagnostics templates et IDE | Angular suit TypeScript au plus près |
instanceof en template |
Moins de boilerplate, templates déclaratifs | Les templates deviennent des expressions TypeScript à part entière |
| Arrow functions en template | Inline callbacks pour les Signals | Fusion du paradigme template et Signal |
Eager |
Sémantique explicite pour Default |
OnPush comme futur mode par défaut annoncé |
| ResourceSnapshot | Composition asynchrone simplifiée | Les Signals absorbent les patterns RxJS complexes |
| Signal Forms 21.2 | Null handling, focus, compat reactive | Chemin de migration progressif consolidé |
Conclusion
Angular 21.2 ne révolutionne pas le framework, mais c'est précisément sa force. En livrant instanceof dans les templates, les arrow functions, ChangeDetectionStrategy.Eager, ResourceSnapshot et les améliorations Signal Forms, l'équipe Angular continue d'aligner le framework avec TypeScript 6 et d'approfondir le modèle Signals.
Pour les équipes ayant adopté les Signals depuis Angular 17, cette mise à jour est une évolution naturelle qui réduit le boilerplate et améliore la lisibilité des templates. Pour celles encore sur Reactive Forms classiques, les améliorations d'interopérabilité et la stabilisation progressive de Signal Forms indiquent clairement la direction à suivre.
Migrez via ng update @angular/core@21.2, adoptez instanceof et les arrow functions dans vos templates, et explorez ResourceSnapshot pour vos pages à sources multiples. Angular 22, attendu fin 2026, devrait franchir un nouveau cap avec OnPush comme mode par défaut.
- Documentation Angular :
angular.dev/guide/templates - Changelog officiel :
github.com/angular/angular/releases/tag/21.2.0 - Signal Forms guide :
angular.dev/guide/forms/signal-forms - Outil de migration :
update.angular.io