# Sintaxis de flujo de control en Angular 2026: @if, @for, @switch y preguntas de entrevista
> Guía completa sobre la sintaxis de flujo de control de Angular con @if, @for y @switch. Aprende a reemplazar las directivas estructurales por bloques de control modernos y prepárate para entrevistas técnicas.
- Published: 2026-09-03
- Updated: 2026-09-03
- Author: Anthony Fillion-Maillet
- Tags: angular, control-flow, templates, signals
- Reading time: 10 min
---
La sintaxis de flujo de control de Angular (`@if`, `@for`, `@switch`) reemplaza las directivas estructurales `*ngIf`, `*ngFor` y `*ngSwitch` que dominaron los templates de Angular durante años. Introducida en Angular 17, estabilizada desde Angular 18 y convertida en la sintaxis predeterminada en Angular 22, este enfoque basado en bloques acerca los templates a las construcciones de programación estándar mientras permite mejores optimizaciones de rendimiento.
> **Migración automática disponible**
>
> Ejecuta `ng generate @angular/core:control-flow` para migrar automáticamente los templates existentes desde las directivas estructurales a la nueva sintaxis de flujo de control. Ambas sintaxis coexisten durante la migración, pero `ngIf`, `ngFor` y `ngSwitch` están deprecadas de forma suave desde Angular 19.
## Por qué Angular reemplazó las directivas estructurales con sintaxis de bloques
Las directivas estructurales requerían importar `CommonModule`, usaban una microsintaxis diferente a JavaScript y forzaban a los desarrolladores a envolver el contenido en `ng-template` para condiciones complejas. La nueva sintaxis de flujo de control resuelve estos problemas:
- Sin imports requeridos: `@if`, `@for` y `@switch` están integrados en el framework
- Sintaxis similar a JavaScript: las condiciones y bucles se leen como código estándar
- Soporte nativo de `@else`: no más referencias a `ng-template` para ramas else
- Mejor tree-shaking: los bloques de flujo de control no utilizados tienen impacto cero en el bundle
La [documentación de Angular](https://angular.dev/guide/templates/control-flow) recomienda adoptar completamente la sintaxis de bloques en todos los proyectos nuevos y migrar progresivamente las bases de código existentes.
## @if: Renderizado condicional sin ng-template
El bloque `@if` renderiza contenido condicionalmente basado en una expresión verdadera. A diferencia de `*ngIf`, soporta ramas `@else if` y `@else` directamente:
```typescript
// user-status.component.ts
import { Component, input } from '@angular/core';
@Component({
selector: 'app-user-status',
template: `
@if (user().role === 'admin') {
} @else if (user().role === 'editor') {
} @else {
}
`
})
export class UserStatusComponent {
user = input.required<{ role: string }>();
}
```
El bloque `@if` también soporta alias de variables con la palabra clave `as`, que extrae valores de expresiones anidadas:
```html
@if (user().profile?.settings?.theme; as theme) {
Tema actual: {{ theme }}
}
```
Este patrón es particularmente útil cuando se trabaja con datos asíncronos u objetos profundamente anidados, ya que la variable con alias solo se define cuando la condición es verdadera.
## @for: Iteración con expresión track obligatoria
El bloque `@for` itera sobre cualquier iterable de JavaScript, con optimizaciones para arrays. A diferencia de `*ngFor`, requiere una expresión `track`:
```typescript
// product-list.component.ts
import { Component, input } from '@angular/core';
interface Product {
id: string;
name: string;
price: number;
}
@Component({
selector: 'app-product-list',
template: `
@for (product of products(); track product.id) {
} @empty {
No hay productos disponibles
}
`
})
export class ProductListComponent {
products = input.required();
}
```
La expresión `track` le indica a Angular cómo identificar cada elemento entre re-renderizados. Elegir la propiedad de tracking correcta impacta directamente el rendimiento:
| Expresión Track | Caso de uso | Impacto en rendimiento |
|-----------------|----------|-----------------|
| `track item.id` | Elementos con identificadores únicos | Óptimo: actualizaciones DOM mínimas |
| `track $index` | Listas estáticas que nunca se reordenan | Aceptable: re-renderizado completo al reordenar |
| `track item` | Tracking por referencia | Malo: nueva referencia = nuevo nodo DOM |
### Variables contextuales en bloques @for
Angular proporciona variables implícitas dentro de los bloques `@for` que exponen metadatos de iteración:
```html
@for (item of items(); track item.id; let i = $index, isLast = $last) {
{{ i + 1 }}. {{ item.name }}
}
```
Variables contextuales disponibles:
- `$index`: posición base cero
- `$count`: número total de elementos
- `$first`, `$last`: flags booleanos para el primer/último elemento
- `$even`, `$odd`: flags booleanos basados en la paridad del índice
## @switch: Ramificación condicional type-safe
El bloque `@switch` proporciona pattern matching exhaustivo con comparación de igualdad estricta (`===`):
```typescript
// status-badge.component.ts
import { Component, input } from '@angular/core';
type OrderStatus = 'pending' | 'processing' | 'shipped' | 'delivered';
@Component({
selector: 'app-status-badge',
template: `
@switch (status()) {
@case ('pending') {
Pendiente
}
@case ('processing') {
Procesando
}
@case ('shipped') {
Enviado
}
@case ('delivered') {
Entregado
}
}
`
})
export class StatusBadgeComponent {
status = input.required();
}
```
A diferencia del `switch` de JavaScript, el `@switch` de Angular no tiene comportamiento fallthrough. Las condiciones múltiples que apuntan al mismo bloque requieren instrucciones `@case` consecutivas:
```html
@switch (userRole()) {
@case ('admin')
@case ('superadmin') {
}
@case ('editor')
@case ('reviewer') {
}
@default {
}
}
```
### Verificación de tipos exhaustiva con @default never
Angular 22 soporta verificación de exhaustividad en tiempo de compilación. Usar `@default never;` declara que no deberían existir casos restantes:
```typescript
type Theme = 'light' | 'dark' | 'system';
// Error de compilación si falta un valor Theme en los bloques @case
@switch (theme()) {
@case ('light') { /* ... */ }
@case ('dark') { /* ... */ }
@case ('system') { /* ... */ }
@default never;
}
```
Agregar un nuevo valor a la unión `Theme` dispara un error de compilación, forzando a los desarrolladores a manejar todos los casos.
## Integración de la sintaxis de flujo de control con Angular Signals
Los bloques de flujo de control se integran perfectamente con [Angular Signals](/technologies/angular/interview-questions/angular-signals), permitiendo reactividad de grano fino:
```typescript
// dashboard.component.ts
import { Component, computed, signal } from '@angular/core';
@Component({
selector: 'app-dashboard',
template: `
@if (isLoading()) {
} @else if (hasError()) {
} @else {
@for (item of filteredItems(); track item.id) {
} @empty {
Ningún elemento coincide con tus filtros
}
}
`
})
export class DashboardComponent {
items = signal- ([]);
filter = signal('');
isLoading = signal(true);
hasError = signal(false);
errorMessage = signal('');
// El signal computed solo recalcula cuando las dependencias cambian
filteredItems = computed(() =>
this.items().filter(item =>
item.name.toLowerCase().includes(this.filter().toLowerCase())
)
);
}
```
Cuando un signal se actualiza, Angular reevalúa solo los bloques de flujo de control afectados. Combinado con la [detección de cambios zoneless de Angular](/blog/angular/angular-19-zoneless-change-detection-performance), esto permite un renderizado altamente optimizado.
## Preguntas de entrevista frecuentes sobre el flujo de control de Angular
Las entrevistas técnicas frecuentemente evalúan la comprensión de la sintaxis de flujo de control, especialmente las diferencias con las directivas estructurales.
### Pregunta 1: ¿Por qué track es obligatorio en @for?
La expresión `track` proporciona a Angular una identidad estable para cada elemento. Sin ella, Angular no puede determinar eficientemente qué nodos DOM crear, actualizar o destruir cuando la colección cambia. Hacer `track` obligatorio fuerza a los desarrolladores a tomar una decisión explícita sobre la identidad, evitando las trampas de rendimiento del tracking implícito por referencia.
### Pregunta 2: ¿Cómo difiere @if de *ngIf con async pipe?
Ambos pueden manejar observables, pero `@if` con signals proporciona acceso síncrono a los valores:
```typescript
// Con *ngIf y async pipe (legacy)
{{ user.name }}
// Con @if y signals (moderno)
@if (user(); as user) {
{{ user.name }}
}
```
El enfoque con signals evita la gestión de subscripciones y se integra mejor con la detección de cambios de Angular.
### Pregunta 3: ¿Puede @switch reemplazar cadenas complejas @if/@else if?
`@switch` debería usarse cuando se compara una sola expresión con múltiples valores discretos. Para condiciones booleanas complejas que involucran diferentes expresiones, `@if/@else if` sigue siendo más apropiado:
```html
@switch (status()) {
@case ('active') { ... }
@case ('inactive') { ... }
}
@if (isAdmin() && hasPermission('write')) {
...
} @else if (isEditor()) {
...
}
```
### Pregunta 4: ¿Qué sucede con @for cuando la colección está vacía?
Cuando el iterable no contiene elementos, Angular omite completamente el contenido del `@for`. El bloque opcional `@empty` renderiza contenido de respaldo en este caso. Sin `@empty`, no se renderiza nada, lo cual difiere de algunos frameworks que renderizan un contenedor vacío.
## Migración desde directivas estructurales al flujo de control
El CLI de Angular proporciona un schematic de migración automatizado:
```bash
# Migrar proyecto completo
ng generate @angular/core:control-flow
# Migrar directorio específico
ng generate @angular/core:control-flow --path=src/app/features
```
El schematic maneja la mayoría de las transformaciones automáticamente, pero hay que revisar la salida para casos límite:
| Directiva estructural | Equivalente flujo de control |
|---------------------|------------------------|
| `*ngIf="condition"` | `@if (condition) { }` |
| `*ngIf="condition; else elseBlock"` | `@if (condition) { } @else { }` |
| `*ngFor="let item of items"` | `@for (item of items; track item) { }` |
| `*ngFor="let item of items; index as i"` | `@for (item of items; track item; let i = $index) { }` |
| `[ngSwitch]` + `*ngSwitchCase` | `@switch` + `@case` |
Después de la migración, elimina los imports de `CommonModule` de los componentes standalone que ya no usan directivas estructurales.
## Puntos clave sobre la sintaxis de flujo de control de Angular
- La sintaxis de flujo de control de Angular (`@if`, `@for`, `@switch`) es el enfoque recomendado desde Angular 19, reemplazando las directivas estructurales
- La expresión `track` en `@for` es obligatoria: usa identificadores únicos como `item.id` para rendimiento óptimo, evita `track item` que causa re-renderizados basados en referencias
- `@if` soporta ramas nativas `@else if` y `@else` sin `ng-template`, y la palabra clave `as` crea alias para valores verdaderos para reutilización
- `@switch` usa igualdad estricta y no tiene fallthrough: las instrucciones `@case` consecutivas apuntan al mismo bloque, y `@default never;` habilita la verificación de tipos exhaustiva
- Los bloques de flujo de control se integran con [Signals](/technologies/angular/interview-questions/angular-signals) para reactividad de grano fino: solo los bloques afectados se re-renderizan cuando los signals se actualizan
- Ejecuta `ng generate @angular/core:control-flow` para migrar automáticamente los templates existentes
---
Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack.
HTML version of this page: https://sharpskill.dev/es/blog/angular/angular-control-flow-syntax-if-for-switch-guide