Limbajul de expresii din template s-a apropiat mult de TypeScript în ultimele versiuni. Pe lângă variabilele locale @let, ai acum template literals, regex, spread, arrow functions, instanceof și chiar comentarii în interiorul tag-urilor. Scopul: mai puțin cod „de legătură” în clasă pentru lucruri simple. Bazele de dedesubt — {{ }}, [ ], ( ) și [( )] — sunt la Data binding.
| Funcționalitate | Versiune | Exemplu |
|---|---|---|
@let | v18.1 (stabil v19) | @let total = price() * qty(); |
| Template literals | v20 | {{ `Salut ${name()}` }} |
**, in, void, typeof | v19–v20 | 'x' in obj |
| Expresii regulate | /^\d+$/.test(v) | |
| Spread (array, obiect, argumente) | [...a, ...b] | |
| Arrow functions | items.filter(i => i.ok) | |
instanceof | err instanceof HttpErrorResponse | |
| Comentarii în tag-uri | <div // notă | |
?. întoarce undefined | user?.name |
@let: variabile locale în template
@let declară o variabilă read-only, vizibilă în blocul curent și în blocurile copil. Se recalculează automat la fiecare randare. E perfect pentru a evita repetarea unei expresii lungi sau a unui async pipe folosit de mai multe ori.
<!-- @let declară o variabilă locală, read-only, vizibilă în blocul curent și în copii -->
@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 /> }
<!-- ideal pentru expresii lungi folosite de mai multe ori -->
@let address = order().customer.billing.address;
<p>{{ address.street }}, {{ address.city }}, {{ address.zip }}</p>
<!-- și în interiorul unui @for, per element -->
@for (p of products(); track p.id) {
@let finalPrice = p.discount ? p.price * (1 - p.discount) : p.price;
<span>{{ finalPrice | currency: 'RON' }}</span>
}4 din 5 produse · total 2900 lei
- Mouse150 lei
- Tastatură-20%360 lei
- Căști900 lei
- Monitor-10%1260 lei
Expresiile noi, pe scurt
<!-- Template literals (v20) -->
<p>{{ `Salut, ${user().name}! Ai ${count()} mesaje.` }}</p>
<img [alt]="`Avatarul lui ${user().name}`" />
<!-- Operatori: ** (putere), in, typeof, void (v20) -->
{{ 2 ** 10 }}
@if ('discount' in product) { ... }
@if (typeof value === 'string') { ... }
<button (click)="void save()">Salvează</button>
<!-- Expresii regulate (v21.0) -->
@let valid = /^07[0-9]{8}$/.test(phone());
<!-- Spread în array-uri, obiecte și argumente (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) — clasa trebuie expusă din componentă -->
@if (error() instanceof HttpErrorResponse) { <p>Eroare HTTP {{ error().status }}</p> }instanceof cere ca clasa să fie expusă
Template-ul vede doar membrii componentei (nu variabilele globale sau importurile), așa că o clasă folosită cu instanceof trebuie pusă într-un câmp.
import { HttpErrorResponse } from '@angular/common/http';
@Component({
template: `
@if (error() instanceof HttpErrorResponse) {
<p>Serverul a răspuns cu {{ error().status }}</p>
} @else if (error()) {
<p>Eroare neașteptată</p>
}
`,
})
export class ErrorView {
readonly error = input<unknown>();
// template-ul vede doar membrii componentei, deci clasa trebuie expusă:
protected readonly HttpErrorResponse = HttpErrorResponse;
}Comentarii în interiorul tag-urilor v22
<!-- v22: comentarii în interiorul tag-urilor -->
<button
// TODO: înlocuiește cu directiva de tooltip
class="btn"
/* temporar dezactivat:
[disabled]="saving()" */
(click)="save()"
>
Salvează
</button>Schimbare importantă în v22: ?. întoarce undefined
Ca să se alinieze cu TypeScript, optional chaining în template întoarce acum undefined (nu null). Migrarea ng update dezactivează temporar diagnosticele nullishCoalescingNotNullable și optionalChainNotNullable ca să nu-ți pice build-ul; reactivează-le după ce verifici template-urile.
<!-- v22 (breaking): ?. în template întoarce undefined, ca în TypeScript (înainte: null) -->
{{ user()?.name }} <!-- user null → undefined -->
<!-- ✗ comparațiile stricte cu null se schimbă -->
@if (user()?.name === null) { ... } <!-- acum nu mai e niciodată true -->
<!-- ✓ compară cu == null sau folosește ?? -->
@if (user()?.name == null) { ... }
{{ user()?.name ?? 'Anonim' }}Din v22.2 compilatorul permite template-ului să citească și membri private. Recomandarea din style guide rămâne însă protected pentru tot ce folosește template-ul: e clar că membrul e pentru view, nu API public.
Cât de multă logică pun în template?
// Arrow functions și metode în template rulează la FIECARE change detection a componentei.
// Pentru liste mari sau calcule scumpe, mută logica într-un computed():
// în loc de: @let done = tasks().filter((t) => t.done);
readonly done = computed(() => this.tasks().filter((t) => t.done)); // memoizat
// Regulă: template-ul ESTE locul pentru expresii simple și locale.
// Logica de business, sortările scumpe și orice se refolosește → în clasă (computed) sau pipe.- Formatări scurte, un template literal, o condiție simplă.
@letpentru a numi o sub-expresie folosită de mai multe ori.- Filtrări mici pe liste scurte, locale vizualizării.
- Reguli de business („poate utilizatorul să editeze?”).
- Calcule scumpe sau pe liste mari →
computed()(memoizat). - Transformări refolosite în multe componente → pipe.
- Orice ce vrei să testezi unitar.
Capcane frecvente
@letnu se poate reasigna și nu e vizibil înainte de declarare.- Variabilele globale (
Math,JSON,Date, clase importate) nu sunt vizibile în template — expune-le din componentă. {{ { ...obj } }}: un obiect literal în interpolare intră în conflict cu acoladele; folosește-l într-un binding de proprietate sau în@let.