Angular : oubliez $any(), utilisez l'optional chaining
Pubblicato il 19-08-2026 - angularforall in Front
Angular Any Optional-Chaining Safe-Navigation Template-Reference-Variable Nullish-Coalescing Strict-Templates Type-Safety Typescript Narrowing Null-Safety Front-End Bonnes-Pratiques Templates
Pourquoi $any() est un faux ami dans les templates Angular et comment le remplacer par l'optional chaining, les template reference variables et le narrowing.
Sommaire
- Un crash en prod causé par un $any()
- À quoi sert vraiment $any() (et pourquoi il existe)
- Le piège démontré : $any(user).name vs user?.name
- Le cas du $event.target : contourner le typage
- Template reference variable vs cast as HTMLInputElement
- Appels de fonction et de méthode en toute sécurité
- Le cas des unions : narrowing runtime
- ?? et court-circuit : compléter le ?.
- strictTemplates : rendre les erreurs visibles
- Checklist : quand utiliser quoi
- Conclusion
Un crash en prod causé par un $any() Vendredi, 17 h. Le tableau de bord est en production depuis dix minutes quand Sentry s'illumine : TypeError: Cannot read properties of null (reading 'name'). Le coupable ? Une seule ligne dans un template Angular :
Bonjour {{ $any(user).name }}
Le développeur avait écrit $any(user) pour « faire taire le compilateur » qui se plaignait. Le build est passé. Les tests aussi. Et pourtant, dès qu'un utilisateur arrive sur la page avant que la requête HTTP n'ait renvoyé son profil, user vaut null, et l'application plante. $any() n'avait protégé de rien : il avait juste caché le problème jusqu'en production .
Attention aux assistants IA. Beaucoup de générateurs de code (ChatGPT, Copilot et consorts) proposent encore $any(...) dès qu'une erreur de typage apparaît dans un template, parce que c'est le moyen le plus rapide de « faire compiler ». C'est un très mauvais réflexe : relisez systématiquement le code généré et remplacez chaque $any() par l'outil adapté — optional chaining, template reference variable ou narrowing. Une suggestion qui compile n'est pas une suggestion qui est correcte.
Cet article démonte une idée reçue tenace : non, $any(user).name n'est pas un « raccourci pratique ». C'est un piège qui combine deux défauts. À l'inverse, l'optional chaining (?., ?.(), ?.[]) et les template reference variables résolvent réellement le problème : ils conservent la sécurité de type et protègent à l'exécution. Chaque section répond à la même question : « en quoi ça m'évite un bug ? »
À quoi sert vraiment $any() (et pourquoi il existe)
$any() est une fonction spéciale du langage de template Angular. Elle ne fait rien à l'exécution : elle se contente de dire au compilateur de templates « considère que cette expression est de type any, arrête de la vérifier ». C'est l'équivalent template du as any de TypeScript.
{{ $any(data).champNonType }}
Pourquoi cette fonction existe-t-elle ? Pour un cas légitime et rare : quand le typage est réellement impossible ou faux. Par exemple, l'interop avec une librairie JavaScript sans définitions de types, ou un faux positif du compilateur sur une structure dynamique. Dans ces situations, $any() est une soupape de secours volontaire et assumée.
Le problème n'est donc pas l'existence de $any(), mais son usage par défaut : l'utiliser comme une rustine dès qu'une erreur rouge apparaît dans l'éditeur. Car ce que beaucoup oublient, c'est que $any() n'a aucun effet runtime . Il ne crée pas de garde, ne vérifie pas la nullité, ne sécurise rien. Il dit juste « tais-toi » au compilateur.
Retenez cette phrase : $any() agit à la compilation, jamais à l'exécution. C'est la racine de toute la confusion. On croit gagner de la sécurité ; on perd en réalité la seule qu'on avait — celle du compilateur.
Le piège démontré : $any(user).name vs user?.name
Prenons le composant qui a planté en production. user peut être null le temps du chargement. Comparons les deux écritures, ligne à ligne.
// profil.component.ts import { Component, signal } from '@angular/core'; interface User { name: string; } @Component({ selector: 'app-profil', standalone: true, templateUrl: './profil.component.html', }) export class ProfilComponent { // user vaut null tant que la requête HTTP n'a pas répondu user = signal(null); }
Bonjour {{ $any(user()).name }}
Bonjour {{ user()?.name }}
Tableau comparatif : deux défauts contre zéro
Critère $any(user).name user?.name Faute de frappe (.naem) ❌ Passe à la compilation ✅ Erreur de compilation Renommage de propriété refactoré ❌ Non suivi (silencieux) ✅ Suivi par l'IDE Autocomplétion de l'éditeur ❌ Perdue ✅ Conservée Si user est null ❌ Crash runtime ✅ Court-circuit à undefined Bug visible en… ❌ Production ✅ Compilation / dev
Le contraste central : $any() cumule deux défauts (pas de type safety + pas de sécurité runtime). ?. apporte deux garanties (type safety conservée + court-circuit propre). Ce n'est pas « légèrement mieux » : c'est l'opposé exact.
Le cas du $event.target : contourner le typage
Voici l'usage de $any() le plus répandu — et le plus instructif, parce qu'ici le problème n'est même pas un null, mais un typage trop large .
Pourquoi ce $any() ? Parce que $event.target est typé EventTarget par le DOM — une interface générique qui ne possède pas de propriété .value . Le compilateur a raison de refuser : tous les EventTarget ne sont pas des . Ici, $any() ne masque pas un null : il masque le fait que vous affirmez sans preuve que la cible est un champ texte.
La Solution Idiomatique En Angular N'est Pas De Mentir Au Compilateur, Mais De Lui Donner La Bonne Information Dès La Source — Avec Une Template Reference Variable La variable #q est, pour Angular, de type HTMLInputElement. Donc q.value est connu, typé string, autocomplété, et vérifié. Aucun $any(), aucune perte de sécurité, et le code est même plus court à lire.
Bonus accessibilité et lisibilité : la template reference variable décrit explicitement ce qu'est l'élément. Un relecteur comprend immédiatement que q est l'input de recherche, là où $any($event.target) n'apprend rien sur la nature réelle de la cible.
Template reference variable vs cast as HTMLInputElement
On pourrait objecter : « je peux aussi caster côté composant ». Effectivement, beaucoup déplacent le problème dans la classe TypeScript :
// ❌ Le cast :
une assertion qui peut MENTIR onInput(event: Event): void { // 'as HTMLInputElement' n'est PAS vérifié : c'est une promesse non tenue const value = (event.target as HTMLInputElement).value; this.query.set(value); }
Le mot-clé as est une assertion de type : il dit au compilateur « crois-moi sur parole ». Si l'événement venait en réalité d'un autre élément (un contenteditable, une délégation d'événement mal câblée), .value serait undefined à l'exécution — et le compilateur n'aurait rien vu. Le cast déplace le risque, il ne l'élimine pas.
// onInput reçoit directement un string typé et vérifié onInput(value: string): void { this.query.set(value); }
Pourquoi le #ref est supérieur au cast
Aspect as HTMLInputElement #q (template ref) Nature Assertion (promesse) Type réel inféré par Angular Vérifié à la compilation ❌ Non (on fait confiance) ✅ Oui Peut mentir / casser au runtime ❌ Oui si la cible diffère ✅ Non, l'élément EST l'input Lisibilité du template Logique éclatée dans la classe Intention claire et locale La règle : une template reference variable est vérifiée par le compilateur (Angular connaît le type réel de l'élément), alors qu'un cast as est seulement cru . Préférez toujours ce qui est vérifié à ce qui est promis.
Appels de fonction et de méthode en toute sécurité
Le danger de $any() est encore plus net sur les appels . Avec $any(obj).fn(), le compilateur ne vérifie ni que obj existe, ni que fn existe, ni que sa signature est respectée. C'est trois portes ouvertes vers le crash. L'optional chaining offre un opérateur précis pour chaque cas.
- — l'objet porteur peut être null
{{ user?.getName() }}
- () — la méthode elle-même peut être absente
{{ user.getName?.() }} cb?.() — un callback @Input optionnel
// widget.component.ts import { Component, input } from '@angular/core'; @Component({ selector: 'app-widget', standalone: true, // onSave est optionnel : le parent peut ne pas le fournir template: `Enregistrer`, }) export class WidgetComponent { onSave = input void) | undefined>(); save(): void { // ✅ cb?.() : appelé seulement si le parent a passé un callback this.onSave()?.(); // ❌ $any(this.onSave)() crasherait si onSave est undefined } }
- [] — accès indexé sur un tableau possiblement null
{{ items?.[0]?.label }}
Le contraste avec $any()
Écriture Vérifie l'existence ? Vérifie la signature ? Si absent ? $any(obj).fn() ❌ Non ❌ Non ? Crash obj?.fn() ✅ obj ✅ fn ↩️ undefined obj.fn?.() obj requis ✅ fn ↩️ undefined arr?.[i]?.x ✅ arr + index — ↩️ undefined
Subtilité vantaggioso : obj?.fn() court-circuite si obj est nul, tandis que obj.fn?.() court-circuite si la méthode est nulle. On peut combiner les deux — obj?.fn?.() — quand l'objet et la méthode sont incertains.
Le cas des unions : narrowing runtime
Parfois, mieux typer ne suffit pas : il faut valider la valeur à l'exécution. Prenons un tri dont la direction est une union stricte :
// table.component.ts import { Component, signal } from '@angular/core'; type SortDir = 'asc' | 'desc' | ''; @Component({ / ... / }) export class TableComponent { sort = signal(''); // ... }
Si l'utilisateur choisit la direction via un , la valeur brute lue est un string quelconque — pas un SortDir. Lui faire confiance (avec un cast ou un $any()) reviendrait à accepter n'importe quelle chaîne, y compris une valeur invalide qui casserait votre logique de tri plus loin.
- Aucun Croissant Décroissant // Un petit type guard valide la valeur AVANT de l'accepter private isSortDir(v: string): v is SortDir { return v === 'asc' || v === 'desc' || v === ''; } setSort(value: string): void { // narrowing : on ne met le signal à jour que si la valeur est légale if (this.isSortDir(value)) { this.sort.set(value); // value est ici typé SortDir, garanti valide } // sinon : on ignore (ou on log) — l'état reste cohérent }
La différence est de nature, pas de degré. $any() dirait « fais comme si c'était un SortDir » sans rien vérifier ; le type guard vérifie réellement à l'exécution que la valeur appartient à l'union. C'est donc plus sûr , pas seulement mieux typé : une valeur aberrante ne peut jamais entrer dans votre état. Pourquoi ça évite un bug : les données qui viennent du DOM, du réseau ou de l'URL sont par nature des string non fiables. Le narrowing runtime est la frontière qui empêche une valeur invalide de contaminer la suite de votre logique métier.
- et court-circuit : compléter le ?.
Un point essentiel souvent mal compris : ?. court-circuite vers undefined, pas vers une exception et pas vers une chaîne vide. Concrètement, {{ user()?.name }} affiche une chaîne vide si user() est null (Angular n'affiche pas le mot « undefined »), mais la valeur de l'expression est bien undefined. Cela a deux conséquences pratiques.
- Fournir une valeur par défaut avec ??
Bonjour {{ user()?.name ?? 'invité' }} Notez bien : ?? ne se déclenche que pour null et undefined, contrairement à || qui se déclencherait aussi pour 0, '' ou false. Pour un repli de valeur, ?? est presque toujours le bon choix.
- L'impact sur les pipes
{{ user()?.name | translate }} {{ (user()?.name ?? 'Anonyme') | uppercase }} Le duo ?. + ?? couvre l'immense majorité des affichages : ?. traverse la chaîne sans planter, ?? fournit le repli quand un maillon manque. Ensemble, ils remplacent à la fois le $any() et les *ngIf="user" défensifs qui alourdissaient les templates d'avant.
strictTemplates : rendre les erreurs visibles
Tout ce qui précède repose sur une condition : que le compilateur de templates vérifie réellement vos expressions. C'est le rôle de strictTemplates, activé via les strict checks d'Angular (option strictTemplates dans tsconfig.json).
// tsconfig.json { "angularCompilerOptions":
{ // Vérifie les types dans les templates aussi strictement que dans le TS "strictTemplates": true } }
Sans strictTemplates, le compilateur est laxiste : il laisse passer des accès douteux et l'intérêt de ?. face à $any() s'efface, car les fautes ne sont de toute façon pas détectées. Avec lui, écrire user.name alors que user peut être null devient une erreur de compilation explicite — exactement le moment où l'on veut être alerté.
Le cercle vertueux : strictTemplates transforme les bugs de null et les fautes de frappe en erreurs de compilation . C'est précisément ce que $any() sabote en désactivant la vérification. Activer l'un et bannir l'autre, c'est la même démarche : déplacer les erreurs de la production vers l'éditeur.
Les nouveaux projets générés par ng new activent strictTemplates par défaut depuis plusieurs versions. Si votre projet est ancien, l'activer est l'une des améliorations de fiabilité les plus rentables que vous puissiez faire — et elle révélera probablement les $any() qui dormaient dans votre code.
Checklist : quand utiliser quoi
Voici la grille de décision à garder sous les yeux pendant vos revues de code. Pour chaque situation, l'outil le plus sûr.
Le Bon Outil Pour Chaque Cas
- ?. — accéder à une propriété quand l'objet peut être null/undefined
- ?.() — appeler une méthode ou un callback qui peut ne pas exister
- ?.[] — accès indexé sur un tableau/objet possiblement absent
- ?? — fournir une valeur par défaut (uniquement sur null/undefined)
- #ref — lire un élément du DOM (.value, .checked…) avec son vrai type
- Type guard (narrowing) — valider une string contre une union avant de l'accepter
- $any() — rarement : interop sans types, prototype jetable, faux positif documenté
Table de correspondance « problème → solution » Le problème ❌ Le réflexe $any() ✅ La bonne solution Objet potentiellement null $any(user).name user?.name Lire la valeur d'un input $any($event.target).value #q + q.value Appeler une méthode incertaine $any(obj).fn() obj?.fn?.() Callback @Input optionnel $any(cb)() cb?.() Valeur à valider (union) $any(s.value) type guard + set() Valeur par défaut d'affichage $any(...) || 'x' ... ?? 'x'
Règle mnémotechnique pour la revue de code : chaque $any() est une question, pas une réponse. Demandez-vous « contre quoi me protège-t-il ? ». Si la réponse est « rien à l'exécution », c'est qu'un ?., un #ref ou un narrowing fera mieux.
Conclusion
$any() a la réputation d'un raccourci pratique, mais c'est un faux ami : il combine l'absence de sécurité de type (les fautes de frappe et les renommages passent inaperçus) et l'absence totale de protection à l'exécution (un null plante toujours). Il ne résout rien — il repousse le bug jusqu'en production, là où il coûte le plus cher.
Les outils du langage moderne couvrent chaque besoin réel : ?. pour traverser une chaîne null sans planter, ?.() et ?.[] pour les appels et les accès indexés incertains, ?? pour les valeurs par défaut, les template reference variables pour lire le DOM avec son vrai type, et le narrowing runtime pour valider les unions. Le tout sous l'œil de strictTemplates, qui transforme les erreurs en alertes de compilation.
Et n'oubliez pas la mise en garde du début : les assistants IA proposent encore trop souvent $any() comme première réponse à une erreur de typage. Relisez, remplacez, et faites de l'optional chaining votre réflexe par défaut. Un template Angular sûr n'est pas un template qui se tait — c'est un template qui ne ment pas.
À Retenir
- $any() agit à la compilation, jamais à l'exécution
- ?. conserve le typage et court-circuite à undefined
- Préférez #ref au cast as : vérifié plutôt que cru
- Validez les unions par narrowing, pas par $any()
- Activez strictTemplates et chassez les $any() résiduels
Précédent Angular 22 : OnPush par défaut, le guide pratique Angular Angular-22 Onpush
Suivant Angular + Apollo GraphQL : requêtes typées
Angular Graphql Apollo-Angular
Questions fréquentes
Parce qu'il cumule deux défauts : il désactive le type-checking (une faute de frappe comme .naem passe à la compilation) et il n'apporte aucune protection runtime (si l'objet est null, le template plante quand même). Il masque le problème au lieu de le résoudre.
$any(user).name coupe le typage et crashe si user est null. user?.name conserve la vérification de type ET court-circuite proprement à undefined si user est null ou undefined : aucune exception, aucun bug en production.
Utilisez une template reference variable : . q est typé HTMLInputElement, donc q.value est vérifié à la compilation. C'est plus sûr qu'un cast as HTMLInputElement, qui est une simple assertion non vérifiée.
Dans de rares cas : interop avec une librairie sans types, prototype jetable, ou contournement temporaire d'un faux positif de typage documenté par un commentaire. Jamais pour gérer un null : pour cela, ?. et ?? sont les bons outils.
Publié/Développé par Mezgani Said
Développeur web full‑stack et formateur spécialisé en Angular, TypeScript et Node.js, il partage sur AngularForAll des ressources complètes pour les développeurs : tutoriels pratiques, guides pas‑à‑pas, outils gratuits, exercices, QCM techniques et retours d’expérience issus de projets réels (SaaS, micro‑frontends, architectures modulaires, applications d’entreprise).
Le site couvre l’ensemble de l’écosystème web : front‑end, intégration HTML/CSS, Composants réutilisables, SEO, back‑end, cloud, IA, CLI, productivité, bien‑être, outils web et métiers du digital.
Un espace pensé pour générer, apprendre, progresser et développer des projets modernes avec des bonnes pratiques professionnelles.
Partager
LinkedIn Facebook Twitter / X Pinterest WhatsApp TikTok Email
URL copiée dans le presse-papiers !
Voir aussi
Front
Angular hostDirectives : composer ses directives
Front
Angular FormArray : formulaires dynamiques
Front
Angular 22 : composants selectorless expliqués
Front
Angular + Apollo GraphQL : requêtes typées
Front
Angular 2026 : tendances et sujets recherchés
Front
Angular + Tailwind CSS 4 : intégration complète
Front
Angular 22 : OnPush par défaut, le guide pratique
Front
Storybook et Angular : documenter ses composants
Front
Angular 22 : skills IA pour coder avec des agents
Front
Angular DI avancée : type-safety junior & expert
