TL;DR:
@if,@fory@switchreemplazan a*ngIf,*ngFory*ngSwitch. No necesitan imports,@elsefunciona sinng-template, y@forexige una expresióntrack. Migra un proyecto existente automáticamente conng generate @angular/core:control-flow.
El control de flujo en las plantillas de Angular ahora es declarativo. Introducido en Angular 17, estable desde Angular 18 y por defecto en proyectos nuevos desde Angular 19, la sintaxis con bloques reemplaza a las directivas estructurales ngIf, ngFor y ngSwitch — hoy soft-deprecated.
Aquí la forma antigua frente a la nueva:
| Directiva estructural | Nuevo Control Flow | Diferencia clave |
|---|---|---|
*ngIf / *ngIf...else | @if / @else | No necesita ng-template ni variables de referencia |
*ngFor | @for | track es obligatorio, ya no opcional |
*ngSwitch / *ngSwitchCase | @switch / @case | No necesita el binding de directiva [ngSwitch] |
@if: condiciones sin ng-template
La sintaxis clásica necesitaba una referencia de plantilla para el caso else:
<div *ngIf="usuarioLogueado; else noAutenticado"></div>
<ng-template #noAutenticado> </ng-template>Y si además querías manejar la rama del usuario logueado por separado:
<div *ngIf="usuarioLogueado; then autenticado else noAutenticado"></div>
<ng-template #autenticado> </ng-template>
<ng-template #noAutenticado> </ng-template>La misma lógica con el nuevo Control Flow:
@if (usuarioLogueado) {
<p>¡Bienvenido de nuevo!</p>
} @else {
<p>Por favor inicia sesión.</p>
}Sin referencias, sin ng-template, y las ramas se leen de arriba a abajo como código normal.
@for: bucles con track obligatorio
La sintaxis antigua:
<div *ngFor="let pais of paises">
<option value="{{ pais.id }}">{{ pais.nombre }}</option>
</div>La nueva sintaxis:
@for (pais of paises; track pais.id) {
<option value="{{ pais.id }}">{{ pais.nombre }}</option>
}La expresión track es obligatoria — esa es la mayor diferencia de comportamiento. Con *ngFor, trackBy era opcional y la mayoría de los proyectos lo omitían, pagando en silencio el costo de reconciliación del DOM. Compara lo que antes hacía falta para el tracking:
<div *ngFor="let pais of paises; trackBy: trackByFn">
<option value="{{ pais.id }}">{{ pais.nombre }}</option>
</div>trackByFn(_: number, pais: Pais) {
return pais.id;
}Todo un método del componente, reemplazado por dos palabras en la plantilla. @for también te da @empty gratis:
@for (pais of paises; track pais.id) {
<option value="{{ pais.id }}">{{ pais.nombre }}</option>
} @empty {
<p>No se encontraron países.</p>
}@switch: casos sin directivas
@switch (lenguaje) {
@case ('python') {
<span>El lenguaje de programación seleccionado es: Python</span>
}
@case ('ruby') {
<span>El lenguaje de programación seleccionado es: Ruby</span>
}
@case ('java') {
<span>El lenguaje de programación seleccionado es: Java</span>
}
@default {
<span>No hay ningún lenguaje de programación seleccionado</span>
}
}Un cambio significativo comparado con:
<section [ngSwitch]="lenguaje">
<span *ngSwitchCase="'python'">El lenguaje de programación seleccionado es: Python</span>
<span *ngSwitchCase="'ruby'">El lenguaje de programación seleccionado es: Ruby</span>
<span *ngSwitchCase="'java'">El lenguaje de programación seleccionado es: Java</span>
<span *ngSwitchDefault>No hay ningún lenguaje de programación seleccionado</span>
</section>La plantilla se reduce considerablemente y se vuelve más legible — y por lo tanto más mantenible.
Vistas Diferibles (Deferrable Views)
La misma sintaxis de bloques impulsa @defer, que carga contenido de la plantilla de forma diferida según triggers:
@for (libro of libros; track libro.id) {
@defer (on viewport) {
<book-card [libro]="libro" />
}
}@defer merece su propia guía a fondo — triggers, placeholders, precarga y el comportamiento de división del bundle están cubiertos en Angular @defer: Lazy Loading de Componentes Fácil.
¿De dónde salió esta sintaxis?
El diseño surgió de las RFC públicas para Control Flow y Deferrable Views. Las primeras propuestas usaban etiquetas al estilo HTML como {#if}, {:else} y {/if}, lo cual podía resultar confuso — tras una votación de la comunidad, el consenso terminó en la sintaxis con @ que tenemos hoy.
Cómo migrar un proyecto existente de *ngIf, *ngFor y *ngSwitch a Control Flow
Un solo comando:
ng generate @angular/core:control-flowConvierte *ngIf, *ngFor y *ngSwitch en todas tus plantillas automáticamente. Ambas sintaxis coexisten, así que también puedes migrar de forma incremental — pero con las directivas antiguas soft-deprecated desde Angular 19, la dirección está clara.
Control Flow es la mitad de Angular declarativo — la otra mitad es el estado reactivo. Revisa Angular Signals: Guía Completa de Estado Reactivo.