En esta página

Templates y control flow nativo

12 min lectura TextoCap. 1 — Fundamentos

Control flow nativo en Angular

Angular 21 usa bloques de control flow nativos directamente en los templates. Esta sintaxis reemplaza las directivas estructurales *ngIf, *ngFor y *ngSwitch de versiones anteriores.

@if / @else

Renderiza contenido condicionalmente:

@if (usuario()) {
  <p>Bienvenido, {{ usuario()!.nombre }}</p>
} @else if (cargando()) {
  <p>Cargando...</p>
} @else {
  <p>Inicia sesión para continuar</p>
}

A diferencia de *ngIf, no necesitas importar ninguna directiva. El bloque @else if es nativo y no requiere ng-template.

@for / @empty

Itera sobre colecciones. La expresión track es obligatoria y debe referenciar una propiedad única:

@for (item of items(); track item.id) {
  <div>{{ item.nombre }}</div>
} @empty {
  <div>No hay elementos para mostrar.</div>
}

El bloque @empty se renderiza cuando la colección esta vacia. Variables contextuales disponibles:

Variable Tipo Descripcion
$index number Índice del elemento actual
$first boolean Es el primer elemento
$last boolean Es el último elemento
$even boolean Índice par
$odd boolean Índice impar
$count number Total de elementos

Ejemplo con variables contextuales:

@for (paso of pasos(); track paso.id; let i = $index) {
  <div [class.activo]="i === pasoActual()">
    Paso {{ i + 1 }}: {{ paso.título }}
  </div>
}

@switch

Evalua una expresión y renderiza el caso correspondiente:

@switch (estado()) {
  @case ('activo') {
    <span class="badge verde">Activo</span>
  }
  @case ('pendiente') {
    <span class="badge amarillo">Pendiente</span>
  }
  @default {
    <span class="badge gris">Desconocido</span>
  }
}

Interpolacion y bindings

Ademas del control flow, los templates Angular usan:

  • Interpolacion: {{ expresión }} para mostrar valores
  • Property binding: [propiedad]="expresión" para enlazar propiedades del DOM
  • Event binding: (evento)="handler()" para escuchar eventos
  • Two-way binding: [(ngModel)]="signal" para formularios (requiere FormsModule)
<!-- Property binding -->
<img [src]="avatar()" [alt]="nombre()" />

<!-- Class binding condicional -->
<div [class.activo]="estaActivo()">Contenido</div>

<!-- Style binding -->
<div [style.color]="estaActivo() ? 'green' : 'gray'">Estado</div>

Pipes en templates

Los pipes transforman valores para la vista:

<p>{{ precio() | currency:'USD' }}</p>
<p>{{ fecha() | date:'longDate' }}</p>
<p>{{ nombre() | uppercase }}</p>

Práctica

  1. Renderiza una lista con @for: Crea un array de al menos 5 objetos (por ejemplo, tareas) y renderizalos con @for usando track por id. Agrega un bloque @empty que muestre un mensaje cuando la lista este vacia.
  2. Agrega filtros con @if y @switch: Implementa botones de filtro que muestren u oculten elementos usando @if. Usa @switch para mostrar un badge diferente segun una propiedad del objeto (por ejemplo, prioridad o categoria).

En la siguiente leccion profundizaremos en Signals, la primitiva reactiva central de Angular 21.

track es obligatorio
En @for, la expresión track es obligatoria. Usa una propiedad única como id. Esto permite a Angular rastrear elementos y optimizar actualizaciones del DOM.
No uses ngIf ni ngFor
Las directivas *ngIf, *ngFor y *ngSwitch estan deprecadas en Angular 21. Usa siempre el control flow nativo: @if, @for, @switch.
import { Component, signal, computed, ChangeDetectionStrategy } from '@angular/core';

interface Producto {
  id: number;
  nombre: string;
  precio: number;
  categoria: 'electronica' | 'ropa' | 'hogar';
  disponible: boolean;
}

@Component({
  selector: 'app-catálogo',
  changeDetection: ChangeDetectionStrategy.OnPush,
  templateUrl: './catálogo.html',
})
export class Catalogo {
  readonly productos = signal<Producto[]>([
    { id: 1, nombre: 'Laptop Pro', precio: 1200, categoria: 'electronica', disponible: true },
    { id: 2, nombre: 'Camiseta Angular', precio: 25, categoria: 'ropa', disponible: true },
    { id: 3, nombre: 'Lampara LED', precio: 45, categoria: 'hogar', disponible: false },
    { id: 4, nombre: 'Teclado mecanico', precio: 89, categoria: 'electronica', disponible: true },
  ]);

  readonly filtro = signal<string>('todos');

  readonly productosFiltrados = computed(() => {
    const f = this.filtro();
    if (f === 'todos') return this.productos();
    return this.productos().filter(p => p.categoria === f);
  });

  readonly total = computed(
    () => this.productosFiltrados().reduce((sum, p) => sum + p.precio, 0)
  );

  cambiarFiltro(categoria: string): void {
    this.filtro.set(categoria);
  }
}