Le langage d’expressions des templates s’est beaucoup rapproché de TypeScript dans les versions récentes. En plus des variables locales @let, vous disposez désormais des template literals, des regex, du spread, des arrow functions, de instanceof et même des commentaires dans les balises. L’objectif : moins de code « de liaison » dans la classe pour les choses simples. Les bases en dessous — {{ }}, [ ], ( ) et [( )] — sont dans Data binding.
| Fonctionnalité | Version | Exemple |
|---|---|---|
@let | v18.1 (stable en v19) | @let total = price() * qty(); |
| Template literals | v20 | {{ `Salut ${name()}` }} |
**, in, void, typeof | v19–v20 | 'x' in obj |
| Expressions régulières | /^\d+$/.test(v) | |
| Spread (tableau, objet, arguments) | [...a, ...b] | |
| Arrow functions | items.filter(i => i.ok) | |
instanceof | err instanceof HttpErrorResponse | |
| Commentaires dans les balises | <div // note | |
?. renvoie undefined | user?.name |
@let : variables locales dans les templates
@let déclare une variable en lecture seule, visible dans le bloc courant et dans ses blocs enfants. Elle est recalculée automatiquement à chaque rendu. Idéal pour éviter de répéter une longue expression ou un pipe async utilisé plusieurs fois.
<!-- @let déclare une variable locale en lecture seule, visible dans le bloc courant et dans ses enfants -->
@let user = user$ | async;
@let fullName = user?.firstName + ' ' + user?.lastName;
@let isAdmin = user?.roles?.includes('admin') ?? false;
<h1>{{ fullName }}</h1>
@if (isAdmin) { <app-admin-tools /> }
<!-- idéal pour les longues expressions utilisées plusieurs fois -->
@let address = order().customer.billing.address;
<p>{{ address.street }}, {{ address.city }}, {{ address.zip }}</p>
<!-- et dans un @for, par élément -->
@for (p of products(); track p.id) {
@let finalPrice = p.discount ? p.price * (1 - p.discount) : p.price;
<span>{{ finalPrice | currency: 'RON' }}</span>
}4 produits sur 5 · total 2900 RON
- Souris150 RON
- Clavier-20%360 RON
- Casque900 RON
- Écran-10%1260 RON
Les nouvelles expressions en bref
<!-- Template literals (v20) -->
<p>{{ `Bonjour, ${user().name} ! Vous avez ${count()} messages.` }}</p>
<img [alt]="`Avatar de ${user().name}`" />
<!-- Opérateurs : ** (puissance), in, typeof, void (v20) -->
{{ 2 ** 10 }}
@if ('discount' in product) { ... }
@if (typeof value === 'string') { ... }
<button (click)="void save()">Enregistrer</button>
<!-- Expressions régulières (v21.0) -->
@let valid = /^07[0-9]{8}$/.test(phone());
<!-- Spread dans les tableaux, objets et arguments (v21.1) -->
@let all = [...defaults, ...extra()];
<app-chart [options]="{ ...baseOptions, color: accent() }" />
{{ max(...values()) }}
<!-- Arrow functions (v21.2) -->
@let done = tasks().filter((t) => t.done);
<app-list [sortFn]="(a, b) => a.name.localeCompare(b.name)" />
<!-- instanceof (v21.2) — la classe doit être exposée depuis le composant -->
@if (error() instanceof HttpErrorResponse) { <p>Erreur HTTP {{ error().status }}</p> }instanceof exige que la classe soit exposée
Le template ne voit que les membres du composant (ni les variables globales ni les imports), donc une classe utilisée avec instanceof doit être placée dans un champ.
import { HttpErrorResponse } from '@angular/common/http';
@Component({
template: `
@if (error() instanceof HttpErrorResponse) {
<p>Le serveur a répondu avec {{ error().status }}</p>
} @else if (error()) {
<p>Erreur inattendue</p>
}
`,
})
export class ErrorView {
readonly error = input<unknown>();
// le template ne voit que les membres du composant, donc la classe doit être exposée :
protected readonly HttpErrorResponse = HttpErrorResponse;
}Commentaires dans les balises v22
<!-- v22 : commentaires dans les balises -->
<button
// TODO: remplacer par la directive de tooltip
class="btn"
/* temporairement désactivé :
[disabled]="saving()" */
(click)="save()"
>
Enregistrer
</button>Changement important en v22 : ?. renvoie undefined
Pour s’aligner sur TypeScript, l’optional chaining dans les templates renvoie désormais undefined (et non null). La migration de ng update désactive temporairement les diagnostics nullishCoalescingNotNullable et optionalChainNotNullable pour que votre build n’échoue pas ; réactivez-les après avoir vérifié les templates.
<!-- v22 (breaking) : ?. dans les templates renvoie undefined, comme en TypeScript (avant : null) -->
{{ user()?.name }} <!-- user null → undefined -->
<!-- ✗ les comparaisons strictes avec null changent -->
@if (user()?.name === null) { ... } <!-- maintenant jamais vrai -->
<!-- ✓ comparez avec == null ou utilisez ?? -->
@if (user()?.name == null) { ... }
{{ user()?.name ?? 'Anonyme' }}Depuis la v22.2, le compilateur permet au template de lire aussi les membres private. La recommandation du guide de style reste protected pour tout ce que le template utilise : cela montre clairement que le membre est destiné à la vue, et non à une API publique.
Quelle part de logique mettre dans le template ?
// Les arrow functions et les méthodes dans les templates s’exécutent à CHAQUE Change Detection du composant.
// Pour de grandes listes ou des calculs coûteux, déplacez la logique dans un computed() :
// au lieu de : @let done = tasks().filter((t) => t.done);
readonly done = computed(() => this.tasks().filter((t) => t.done)); // mémoïsé
// Règle : le template EST l’endroit des expressions simples et locales.
// Logique métier, tris coûteux et tout ce qui est réutilisé → dans la classe (computed) ou une pipe.- Mise en forme courte, un template literal, une condition simple.
@letpour nommer une sous-expression utilisée plusieurs fois.- Petits filtres sur des listes courtes, locaux à la vue.
- Règles métier (« l’utilisateur peut-il modifier ? »).
- Calculs coûteux ou grandes listes →
computed()(mémoïsé). - Transformations réutilisées dans de nombreux composants → une pipe.
- Tout ce que vous voulez tester unitairement.
Pièges fréquents
@letne peut pas être réaffecté et n’est pas visible avant sa déclaration.- Les variables globales (
Math,JSON,Date, classes importées) ne sont pas visibles dans le template : exposez-les depuis le composant. {{ { ...obj } }}: un littéral d’objet dans une interpolation entre en conflit avec les accolades ; utilisez-le dans un property binding ou dans@let.