Blocurile @if, @for și @switch sunt sintaxa nativă de control a template-urilor (din v17, stabile din v18). Înlocuiesc directivele structurale *ngIf, *ngFor și ngSwitch: nu trebuie importate, au tipare mai bune, sunt mai rapide și mai ușor de citit.
@if: condiții, else if, alias
@if (user(); as u) {
<p>Salut, {{ u.name }}!</p>
} @else if (loading()) {
<app-spinner />
} @else {
<a routerLink="/login">Autentifică-te</a>
}
<!-- `as` creează un alias tipizat (ne-null) pentru blocul curent -->
@if (order()?.items?.length; as count) {
<span>{{ count }} produse</span>
}@for: liste cu track obligatoriu
track e obligatoriu. Îi spune lui Angular cum să recunoască un element între două randări, ca să refolosească DOM-ul în loc să-l recreeze. Blocul @empty apare când lista e goală.
<ul>
@for (task of tasks(); track task.id; let i = $index, last = $last) {
<li [class.last]="last">{{ i + 1 }}. {{ task.title }}</li>
} @empty {
<li>Nu ai niciun task.</li>
}
</ul>
<!-- Variabile disponibile în @for:
$index $count $first $last $even $odd -->- 1/3 · Migrează *ngIf la @if$first
- 2/3 · Adaugă track pe toate @for
- 3/3 · Șterge CommonModule$last
Cum alegi expresia de track
// ✓ ID stabil și unic — varianta corectă în 95% din cazuri
@for (user of users(); track user.id) { ... }
// ✓ Valori primitive unice (string-uri, numere)
@for (tag of tags(); track tag) { ... }
// $index — doar pentru liste statice, care nu se reordonează / filtrează
@for (step of wizardSteps; track $index) { ... }
// ✗ Obiectul întreg (identitate) când lista e recreată imutabil sau vine de la server:
// fiecare refetch = obiecte noi = TOT DOM-ul distrus și recreat
@for (user of users(); track user) { ... }1. Scrie o notiță lângă Ana. 2. Apasă „Mută primul la final”. Unde a ajuns notița?
Un catalog întreg, cu căutare, sortare și pagini, urmărit după id, găsești la Liste. Același bug cu $index e unul dintre cele patru de la Prima ta aplicație.
@switch, cazuri multiple și verificare exhaustivăv21
@switch (user().role) {
@case ('admin') { <app-admin-dashboard /> }
@case ('editor') { <app-editor-dashboard /> }
@default { <app-viewer-dashboard /> }
}Din v21.1 poți grupa mai multe @case consecutive peste același conținut, iar din v21.2 @default never; transformă un caz uitat într-o eroare de compilare.
<!-- v21.1: mai multe @case consecutive pentru același conținut -->
<!-- v21.2: @default never; → eroare de COMPILARE dacă uiți un caz -->
@let current = status();
@switch (current) {
@case ('draft')
@case ('pending') { <span>În așteptare</span> }
@case ('paid') { <span>Plătită</span> }
@case ('shipped')
@case ('delivered') { <span>Trimisă</span> }
@case ('cancelled') { <span>Anulată</span> }
@default never;
}Sintaxa veche și migrarea
<!-- ✗ Sintaxa veche (încă funcționează, dar e deprecată stilistic) -->
<div *ngIf="user$ | async as user; else loading">{{ user.name }}</div>
<ng-template #loading><app-spinner /></ng-template>
<li *ngFor="let t of tasks; trackBy: trackById; let i = index">{{ t.title }}</li>
<div [ngSwitch]="role">
<app-admin *ngSwitchCase="'admin'" />
<app-viewer *ngSwitchDefault />
</div>Rulează ng generate @angular/core:control-flow — migrarea e automată și sigură. După ea, de obicei poți scoate CommonModule / NgIf / NgFor din imports.
Capcane frecvente
track $indexpe liste dinamice: starea DOM (input-uri, focus, animații) rămâne pe poziție, nu pe element.track itempe date imutabile: fiecare update creează obiecte noi → tot DOM-ul e recreat.- Apeluri scumpe în condiții:
@if (computeSomething())rulează la fiecare change detection. Mută logica într-uncomputed(). @switch (status())+@default never: tipul nu se îngustează pe apelul de semnal; folosește@let s = status();și@switch (s).- Caractere speciale: în template,
@și{ }literale trebuie scrise ca entități (@,{).
Fișă rapidă
@if (cond) { } @else if (other) { } @else { }
@if (expr; as alias) { }
@for (item of items; track item.id; let i = $index, f = $first) { } @empty { }
// $index $count $first $last $even $odd
@switch (value) {
@case (a) @case (b) { } // v21.1: cazuri multiple
@default { } // sau: @default never; (v21.2, exhaustiv)
}
// migrare: ng generate @angular/core:control-flow