Dominando el nuevo Control Flow de Angular: Adiós *ngIf y *ngFor
Angular ha evolucionado significativamente su motor de plantillas. Las directivas estructurales clásicas como *ngIf y *ngFor han servido bien durante años, pero dependían de librerías externas (CommonModule) y tenían limitaciones de rendimiento y tipado.
El nuevo Control Flow está integrado directamente en el compilador.
Bloque @if: Condicionales más limpios
La nueva sintaxis es mucho más intuitiva y se asemeja a JavaScript estándar. Ya no necesitamos contenedores ng-container solo para la lógica.
@if (isLoggedIn) {
<user-profile [user]="user" />
} @else if (isLoading) {
<loading-spinner />
} @else {
<login-button />
}
Ventajas sobre *ngIf
- Sintaxis más limpia: No más importaciones de
CommonModule. - Mejor inferencia de tipos: TypeScript entiende mejor el contexto dentro de los bloques.
- Sin sobrecarga: No crea elementos DOM intermedios innecesarios.
Ejemplo real: Formulario con validación
Veamos un caso de uso típico donde gestionamos distintos estados de un formulario:
@if (form.controls.email; as emailCtrl) {
<input [formControl]="emailCtrl" type="email" />
@if (emailCtrl.hasError('required') && emailCtrl.touched) {
<span class="error">El email es obligatorio</span>
} @else if (emailCtrl.hasError('email')) {
<span class="error">El formato del email no es válido</span>
}
}
Fíjate cómo la variable local emailCtrl (declarada con as) permite acceder al control de forma tipada dentro del bloque.
Bloque @for: Iteraciones optimizadas
El cambio más drástico de rendimiento viene con el bucle @for. Ahora es obligatorio el uso de track para garantizar la eficiencia en el renderizado.
<ul>
@for (item of items; track item.id; let i = $index, e = $even) {
<li [class.gray]="e">
{{ i }} - {{ item.name }}
</li>
} @empty {
<li>No hay elementos en la lista</li>
}
</ul>
Características Clave
- Track: Define una clave única (como un ID) para que Angular sepa exactamente qué nodo del DOM mover, crear o destruir.
- Variables Implícitas: Acceso fácil a
$index,$first,$last,$eveny$odd. - Bloque @empty: Una solución elegante para mostrar contenido cuando el array está vacío, eliminando la necesidad de un
*ngIfexterno.
Ejemplo: Tabla de datos con estados
<table>
<thead>
<tr><th>Nombre</th><th>Rol</th><th>Acciones</th></tr>
</thead>
<tbody>
@for (user of users(); track user.id; let isFirst = $first, isLast = $last) {
<tr [class.border-t-2]="isFirst" [class.border-b-2]="isLast">
<td>{{ user.name }}</td>
<td>{{ user.role }}</td>
<td>
<button (click)="editUser(user)">Editar</button>
</td>
</tr>
} @empty {
<tr>
<td colspan="3" class="text-center py-8">
No se encontraron usuarios
</td>
</tr>
}
</tbody>
</table>
Bloque @switch
El ngSwitch siempre fue un poco verboso. La nueva sintaxis lo simplifica enormemente:
@switch (userRole) {
@case ('admin') {
<admin-dashboard />
}
@case ('editor') {
<editor-tools />
}
@default {
<user-view />
}
}
Comparativa de rendimiento
El nuevo Control Flow ofrece mejoras medibles:
- @for con track: Hasta un 90% más rápido que
*ngForen actualizaciones parciales de listas grandes. - @if: Mejor tree-shaking ya que no depende de directivas importadas.
- Bundle size: Menor tamaño final al eliminar la dependencia de
CommonModule.
Migración
Angular ofrece una herramienta automática para migrar tu proyecto:
ng generate @angular/core:control-flow
Esto escaneará tus plantillas y convertirá las directivas antiguas a la nueva sintaxis automáticamente.