Iteración con @for

Intermedio
Angular
Angular
Actualizado: 24/09/2025

Sintaxis @for y track

La sintaxis @for de Angular representa una evolución significativa en la forma de iterar sobre colecciones en las plantillas. Esta nueva estructura de control reemplaza completamente a la directiva *ngFor, proporcionando mejor rendimiento y una sintaxis más intuitiva que se alinea con los patrones modernos de desarrollo.

Estructura básica de @for

La sintaxis fundamental de @for sigue un patrón declarativo que resulta familiar para desarrolladores que han trabajado con estructuras de control en otros lenguajes:

@for (item of items; track item.id) {
  <div>{{ item.name }}</div>
}

Este bloque @for requiere dos elementos esenciales: la expresión de iteración (item of items) y la función track (track item.id). La expresión de iteración define qué colección recorrer y cómo nombrar cada elemento, mientras que la función track optimiza el rendimiento del renderizado.

La importancia del tracking

El parámetro track no es opcional en @for, a diferencia de trackBy en *ngFor que era opcional. Esta decisión de diseño obliga a los desarrolladores a considerar el rendimiento desde el principio, evitando problemas comunes de renderizado innecesario.

// Componente con lista de usuarios
export class UserListComponent {
  users = [
    { id: 1, name: 'Ana García', email: 'ana@email.com' },
    { id: 2, name: 'Carlos López', email: 'carlos@email.com' },
    { id: 3, name: 'María Rodríguez', email: 'maria@email.com' }
  ];
}
<!-- Template con @for optimizado -->
@for (user of users; track user.id) {
  <div class="user-card">
    <h3>{{ user.name }}</h3>
    <p>{{ user.email }}</p>
  </div>
}

Estrategias de tracking

Angular ofrece múltiples estrategias de tracking según el tipo de datos que manejes:

Para objetos con identificadores únicos:

@for (product of products; track product.id) {
  <div class="product">{{ product.name }} - {{ product.price }}€</div>
}

Para arrays de primitivos:

// Array de strings
categories = ['Tecnología', 'Deportes', 'Música', 'Cocina'];
@for (category of categories; track category) {
  <span class="tag">{{ category }}</span>
}

Usando el índice como tracking:

@for (item of items; track $index) {
  <li>Elemento {{ $index + 1 }}: {{ item }}</li>
}

Variables contextuales disponibles

La sintaxis @for proporciona variables contextuales útiles para crear interfaces más dinámicas:

@for (task of tasks; track task.id) {
  <div class="task-item" 
       [class.first]="$first"
       [class.last]="$last"
       [class.even]="$even">
    
    <span class="task-number">{{ $index + 1 }}</span>
    <span class="task-content">{{ task.title }}</span>
    
    @if ($first) {
      <span class="badge">Primera tarea</span>
    }
    
    @if ($last) {
      <span class="badge">Última tarea</span>
    }
  </div>
}

Las variables contextuales incluyen:

  • $index - Índice actual del elemento (empezando en 0)
  • $first - True si es el primer elemento
  • $last - True si es el último elemento
  • $even - True si el índice es par
  • $odd - True si el índice es impar
  • $count - Número total de elementos en la colección

Tracking con funciones personalizadas

Para casos más complejos, puedes crear funciones de tracking personalizadas en el componente:

export class OrderListComponent {
  orders = [
    { id: 'ORD-001', customer: 'Juan Pérez', total: 125.50 },
    { id: 'ORD-002', customer: 'Ana Silva', total: 89.99 }
  ];

  // Función de tracking personalizada
  trackByOrderId(index: number, order: any): string {
    return order.id;
  }
}
@for (order of orders; track trackByOrderId($index, order)) {
  <div class="order-summary">
    <strong>{{ order.id }}</strong> - {{ order.customer }}
    <span class="total">{{ order.total }}€</span>
  </div>
}

Consideraciones de rendimiento

El tracking correcto es fundamental para el rendimiento de Angular. Cuando la colección cambia, Angular utiliza la función track para determinar qué elementos del DOM pueden reutilizarse:

// ❌ Tracking incorrecto - recrea todos los elementos
@for (user of users; track $index) {
  <div>{{ user.name }}</div>
}

// ✅ Tracking óptimo - reutiliza elementos existentes
@for (user of users; track user.id) {
  <div>{{ user.name }}</div>
}

Con un tracking apropiado, Angular puede optimizar automáticamente las actualizaciones del DOM, manteniendo el estado de componentes anidados y evitando recálculos innecesarios de estilos y animaciones.

La sintaxis @for con tracking obligatorio representa un paso hacia adelante en la optimización por defecto, asegurando que las aplicaciones Angular mantengan un rendimiento consistente incluso con listas grandes y dinámicas.

Iteración de colecciones

La iteración de colecciones con @for va más allá de simples arrays, permitiendo trabajar con diversos tipos de estructuras de datos y situaciones comunes en aplicaciones Angular modernas. Esta flexibilidad hace que @for sea una herramienta versátil para renderizar contenido dinámico desde múltiples fuentes de datos.

Trabajando con diferentes tipos de colecciones

Angular @for puede iterar sobre cualquier estructura que implemente el protocolo iterable de JavaScript, lo que incluye arrays, strings, Maps, Sets y objetos transformados:

Iteración sobre arrays de objetos complejos:

export class ProductCatalogComponent {
  products = [
    {
      id: 1,
      name: 'Laptop Gaming',
      price: 1299.99,
      category: 'Tecnología',
      inStock: true,
      tags: ['gaming', 'portátil', 'alto rendimiento']
    },
    {
      id: 2,
      name: 'Auriculares Bluetooth',
      price: 89.50,
      category: 'Audio',
      inStock: false,
      tags: ['inalámbrico', 'música']
    }
  ];
}
@for (product of products; track product.id) {
  <article class="product-card">
    <header>
      <h3>{{ product.name }}</h3>
      <span class="price">{{ product.price }}€</span>
    </header>
    
    <div class="product-info">
      <span class="category">{{ product.category }}</span>
      <span class="stock-status" [class.out-of-stock]="!product.inStock">
        {{ product.inStock ? 'En stock' : 'Agotado' }}
      </span>
    </div>
    
    <div class="tags">
      @for (tag of product.tags; track tag) {
        <span class="tag">{{ tag }}</span>
      }
    </div>
  </article>
}

Iteración sobre Maps y Sets:

export class ConfigurationComponent {
  // Map para configuraciones clave-valor
  settings = new Map([
    ['theme', 'oscuro'],
    ['language', 'es'],
    ['notifications', 'activadas'],
    ['autoSave', 'habilitado']
  ]);

  // Set para elementos únicos
  availableLanguages = new Set(['es', 'en', 'fr', 'de', 'it']);
}
<!-- Iterando sobre Map entries -->
<div class="settings-panel">
  <h3>Configuración actual</h3>
  @for (setting of settings; track setting[0]) {
    <div class="setting-item">
      <span class="setting-key">{{ setting[0] }}</span>
      <span class="setting-value">{{ setting[1] }}</span>
    </div>
  }
</div>

<!-- Iterando sobre Set -->
<div class="language-selector">
  <h3>Idiomas disponibles</h3>
  @for (language of availableLanguages; track language) {
    <button class="language-btn" [value]="language">
      {{ language.toUpperCase() }}
    </button>
  }
</div>

Manejo de colecciones vacías con @empty

Una característica destacada de @for es el bloque @empty, que proporciona una forma elegante de manejar colecciones vacías sin lógica condicional adicional:

export class NotificationListComponent {
  notifications: Notification[] = [];
  
  loadNotifications() {
    // Simulación de carga de datos
    setTimeout(() => {
      this.notifications = [
        { id: 1, message: 'Nuevo mensaje recibido', type: 'info' },
        { id: 2, message: 'Actualización disponible', type: 'warning' }
      ];
    }, 2000);
  }
}
<div class="notifications-container">
  <h2>Centro de notificaciones</h2>
  
  @for (notification of notifications; track notification.id) {
    <div class="notification" [class]="notification.type">
      <span class="message">{{ notification.message }}</span>
      <button class="dismiss-btn">×</button>
    </div>
  } @empty {
    <div class="empty-state">
      <p>No tienes notificaciones nuevas</p>
      <button (click)="loadNotifications()" class="refresh-btn">
        Actualizar
      </button>
    </div>
  }
</div>

Iteración con transformaciones de datos

Muchas veces necesitas transformar o filtrar datos antes de mostrarlos. Puedes combinar @for con métodos del componente para crear vistas dinámicas:

export class TaskManagerComponent {
  allTasks = [
    { id: 1, title: 'Revisar código', completed: false, priority: 'alta' },
    { id: 2, title: 'Actualizar documentación', completed: true, priority: 'media' },
    { id: 3, title: 'Preparar presentación', completed: false, priority: 'alta' },
    { id: 4, title: 'Responder emails', completed: true, priority: 'baja' }
  ];

  currentFilter = 'all'; // 'all', 'pending', 'completed'

  get filteredTasks() {
    switch (this.currentFilter) {
      case 'pending':
        return this.allTasks.filter(task => !task.completed);
      case 'completed':
        return this.allTasks.filter(task => task.completed);
      default:
        return this.allTasks;
    }
  }

  get tasksByPriority() {
    return this.filteredTasks.reduce((groups, task) => {
      const priority = task.priority;
      if (!groups[priority]) {
        groups[priority] = [];
      }
      groups[priority].push(task);
      return groups;
    }, {} as Record<string, typeof this.allTasks>);
  }
}
<div class="task-manager">
  <!-- Filtros -->
  <div class="filters">
    <button (click)="currentFilter = 'all'" 
            [class.active]="currentFilter === 'all'">Todas</button>
    <button (click)="currentFilter = 'pending'" 
            [class.active]="currentFilter === 'pending'">Pendientes</button>
    <button (click)="currentFilter = 'completed'" 
            [class.active]="currentFilter === 'completed'">Completadas</button>
  </div>

  <!-- Tareas agrupadas por prioridad -->
  @for (priorityGroup of Object.entries(tasksByPriority); track priorityGroup[0]) {
    <div class="priority-group">
      <h3 class="priority-header priority-{{ priorityGroup[0] }}">
        Prioridad {{ priorityGroup[0] }}
      </h3>
      
      @for (task of priorityGroup[1]; track task.id) {
        <div class="task-item" [class.completed]="task.completed">
          <input type="checkbox" [checked]="task.completed">
          <span class="task-title">{{ task.title }}</span>
        </div>
      } @empty {
        <p class="no-tasks">No hay tareas de prioridad {{ priorityGroup[0] }}</p>
      }
    </div>
  }
</div>

Iteración anidada y casos complejos

Para estructuras de datos jerárquicas, @for maneja perfectamente la iteración anidada manteniendo el rendimiento:

export class MenuComponent {
  menuStructure = [
    {
      section: 'Administración',
      icon: 'admin',
      items: [
        { name: 'Usuarios', route: '/admin/users', permissions: ['admin'] },
        { name: 'Configuración', route: '/admin/config', permissions: ['admin'] }
      ]
    },
    {
      section: 'Contenido',
      icon: 'content',
      items: [
        { name: 'Artículos', route: '/content/articles', permissions: ['editor'] },
        { name: 'Medios', route: '/content/media', permissions: ['editor'] },
        { name: 'Comentarios', route: '/content/comments', permissions: ['moderator'] }
      ]
    }
  ];
}
<nav class="sidebar-menu">
  @for (section of menuStructure; track section.section) {
    <div class="menu-section">
      <div class="section-header">
        <i class="icon-{{ section.icon }}"></i>
        <span>{{ section.section }}</span>
      </div>
      
      <ul class="section-items">
        @for (item of section.items; track item.route) {
          <li class="menu-item">
            <a [routerLink]="item.route" class="menu-link">
              {{ item.name }}
            </a>
            
            <!-- Mostrar permisos como badges -->
            <div class="permissions">
              @for (permission of item.permissions; track permission) {
                <span class="permission-badge">{{ permission }}</span>
              }
            </div>
          </li>
        } @empty {
          <li class="no-items">No hay elementos disponibles</li>
        }
      </ul>
    </div>
  } @empty {
    <div class="empty-menu">
      <p>Menú no disponible</p>
    </div>
  }
</nav>

Optimización para colecciones grandes

Cuando trabajas con colecciones extensas, considera técnicas de optimización que complementen el tracking de @for:

export class DataTableComponent {
  allRecords = []; // Array con miles de registros
  pageSize = 50;
  currentPage = 0;

  get paginatedRecords() {
    const start = this.currentPage * this.pageSize;
    const end = start + this.pageSize;
    return this.allRecords.slice(start, end);
  }

  get totalPages() {
    return Math.ceil(this.allRecords.length / this.pageSize);
  }
}
<div class="data-table">
  <!-- Solo renderizar registros visibles -->
  @for (record of paginatedRecords; track record.id) {
    <div class="table-row">
      <span class="cell">{{ record.name }}</span>
      <span class="cell">{{ record.email }}</span>
      <span class="cell">{{ record.department }}</span>
    </div>
  } @empty {
    <div class="no-data">
      <p>No se encontraron registros</p>
    </div>
  }

  <!-- Controles de paginación -->
  <div class="pagination">
    <button [disabled]="currentPage === 0" 
            (click)="currentPage = currentPage - 1">
      Anterior
    </button>
    
    <span>Página {{ currentPage + 1 }} de {{ totalPages }}</span>
    
    <button [disabled]="currentPage === totalPages - 1" 
            (click)="currentPage = currentPage + 1">
      Siguiente
    </button>
  </div>
</div>

La iteración de colecciones con @for proporciona una base sólida para crear interfaces dinámicas y eficientes, adaptándose a las necesidades específicas de cada aplicación mientras mantiene un código limpio y performante.

Fuentes y referencias

Documentación oficial y recursos externos para profundizar en Angular

Documentación oficial de Angular
Alan Sastre - Autor del tutorial

Alan Sastre

Ingeniero de Software y formador, CEO en CertiDevs

Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, Angular es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.

Más tutoriales de Angular

Explora más contenido relacionado con Angular y continúa aprendiendo con nuestros tutoriales gratuitos.

Aprendizajes de esta lección

  • Comprender la sintaxis básica y estructura del bloque @for en Angular.
  • Aprender la importancia y uso del parámetro track para optimizar el renderizado.
  • Manejar diferentes tipos de colecciones (arrays, Maps, Sets) con @for.
  • Utilizar variables contextuales para mejorar la interacción en las plantillas.
  • Implementar iteración anidada, manejo de colecciones vacías y optimización para grandes volúmenes de datos.