Menu
×
   ❮     
HTML CSS JAVASCRIPT SQL PYTHON JAVA PHP HOW TO W3.CSS C C++ C# BOOTSTRAP REACT MYSQL JQUERY EXCEL XML DJANGO NUMPY PANDAS NODEJS DSA TYPESCRIPT ANGULAR ANGULARJS GIT POSTGRESQL MONGODB ASP AI R GO KOTLIN SASS VUE GEN AI SCIPY CYBERSECURITY DATA SCIENCE INTRO TO PROGRAMMING BASH RUST

Angular Lists


Lists display collections of items in the template.


List Rendering Essentials

  • Loop: Use @for with track for stable identity and @empty for empty states.
  • Signals: Store list state in a signal (e.g., items = signal([...])) and update immutably with set()/update().
  • Identity: Track by a stable key (e.g., it.id) to avoid unnecessary DOM work.
  • Derived views: Filter/sort copies of your data for the UI; keep the source list intact (use computed() for derived state).

Note: See Control Flow for @for, Conditional Rendering, and Templates for interpolation and basics.


Basic Lists

  • Use @for to loop; expose the index with let i = $index.
  • Update immutably with signals (e.g., items.update(arr => [...arr, newItem])).
<ul>
  @for (item of items(); let i = $index; track item) {
    <li>{{ i + 1 }}. {{ item }}</li>
  } @empty {
    <li>No items</li>
  }
</ul>

Example

Render a basic list with @for and expose the index:

Example

import { bootstrapApplication } from '@angular/platform-browser';
import { Component, signal } from '@angular/core';

@Component({
  selector: 'app-root',
  standalone: true,
  template: `
    <h3>Lists</h3>
    <ul>
      @for (item of items(); let i = $index; track item) {
        <li>{{ i + 1 }}. {{ item }}</li>
      } @empty {
        <li>No items</li>
      }
    </ul>
    <button (click)="add()">Add Item</button>
    <button (click)="clear()">Clear</button>
    <button (click)="reset()">Reset</button>
  `
})
export class App {
  items = signal(['Angular', 'React', 'Vue']);
  add() { this.items.update(arr => [...arr, 'Svelte']); }
  clear() { this.items.set([]); }
  reset() { this.items.set(['Angular', 'React', 'Vue']); }
}

bootstrapApplication(App);
<app-root></app-root>

Run Example »

Example explained

  • @for (item of items(); let i = $index; track item): Loops over the items signal; exposes the zero-based index as i; uses the primitive item itself as identity.
  • Buttons: add() appends immutably, clear() sets an empty list, reset() restores defaults.
  • @empty: Renders the fallback list item when there are no items.

Notes:

  • No import needed: @for is built into Angular's template syntax; no module import required.
  • Don't mutate in place: With signals, prefer set()/update() to assign a new array and trigger updates.

REMOVE ADS


Lists with track (@for)

  • On list changes, Angular reconciles DOM rows with data items.
  • track provides a stable identity (e.g., an id) to minimize DOM churn and preserve focus/inputs.
  • Legacy equivalence: With *ngFor, use trackBy to achieve the same effect.
@for (it of items(); track it.id) { <li>{{ it.name }}</li> } @empty { <li>No items</li> }

Example

Render lists with a stable identity using track:

Example

import { bootstrapApplication } from '@angular/platform-browser';
import { Component, signal } from '@angular/core';

type Item = { id: number; name: string };

@Component({
  selector: 'app-root',
  standalone: true,
  template: `
    <h3>Lists with track</h3>
    <ul>
      @for (it of items(); let i = $index; track it.id) {
        <li>{{ i + 1 }}. {{ it.name }} (id: {{ it.id }})</li>
      }
    </ul>
    <button (click)="renameFirst()">Rename first</button>
    <button (click)="shuffle()">Shuffle</button>
    <button (click)="add()">Add item</button>
  `
})
export class App {
  items = signal([
    { id: 1, name: 'Angular' },
    { id: 2, name: 'React' },
    { id: 3, name: 'Vue' }
  ]);
  nextId = 4;

  renameFirst() {
    this.items.update(arr => arr.map((it, i) => i === 0 ? { ...it, name: it.name + ' *' } : it));
  }

  shuffle() {
    this.items.update(arr => {
      const copy = [...arr];
      for (let i = copy.length - 1; i > 0; i--) {
        const j = Math.floor(Math.random() * (i + 1));
        [copy[i], copy[j]] = [copy[j], copy[i]];
      }
      return copy;
    });
  }

  add() {
    this.items.update(arr => [...arr, { id: this.nextId++, name: 'New ' + Date.now() }]);
  }
}

bootstrapApplication(App);
<app-root></app-root>

Run Example »

Example explained

  • track it.id: Provides a stable identity so Angular reuses DOM rows when items shuffle or update, preserving focus and local state.
  • renameFirst(): Updates the first item's name immutably (new object reference) to trigger change detection.
  • shuffle(): Randomizes order to demonstrate DOM reuse with track.
  • add(): Appends a new item with a unique id.

Notes:

  • Avoid index identity: Don't use the array index as identity if order can change; use a stable id.
  • Ensure uniqueness: Duplicate id values cause DOM/UI desync. Use unique keys.
  • *ngFor equivalence: With *ngFor, use trackBy to achieve the same behavior.

Filter & Sort

  • Compute a derived view with computed() based on signals.
  • Filter and sort copies of your data; keep the source list intact for easy resets.
import { signal, computed } from '@angular/core';

items = signal([{ name: 'Angular', price: 0 }, { name: 'React', price: 0 }]);
query = signal('');
sortKey = signal<'name' | 'price'>('name');
sortDir = signal<1 | -1>(1);

view = computed(() => {
  const q = query().toLowerCase();
  const dir = sortDir();
  const key = sortKey();
  return items()
    .filter(it => it.name.toLowerCase().includes(q))
    .sort((a, b) => {
      const av: any = (a as any)[key];
      const bv: any = (b as any)[key];
      return av < bv ? -1 * dir : av > bv ? 1 * dir : 0;
    });
});

@for (p of view(); track p.name) {
  <tr>
    <td>{{ p.name }}</td>
    <td>{{ p.price | currency:'USD' }}</td>
  </tr>
}

Example

Filter and sort lists using computed():

Example

import { bootstrapApplication } from '@angular/platform-browser';
import { Component, signal, computed } from '@angular/core';
import { CommonModule } from '@angular/common';

type Product = { name: string; price: number };

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [CommonModule],
  template: `
    <h3>Filter & Sort</h3>
    <div style="display:flex;gap:8px;margin-bottom:8px;">
      <label>Search: <input #q (input)="query.set(q.value)" placeholder="Type to filter..." /></label>
      <button (click)="setSort('name')">Sort by Name</button>
      <button (click)="setSort('price')">Sort by Price</button>
      <button (click)="toggleDir()">{{ sortDir() === 1 ? 'Asc' : 'Desc' }}</button>
    </div>

    <table style="width:100%;border-collapse:collapse;">
      <thead>
        <tr><th style="border:1px solid #ddd;padding:8px;background:#f7f7f7;">Name</th><th style="border:1px solid #ddd;padding:8px;background:#f7f7f7;width:140px;">Price</th></tr>
      </thead>
      <tbody>
        @for (p of view(); track p.name) {
          <tr>
            <td style="border:1px solid #ddd;padding:8px;">{{ p.name }}</td>
            <td style="border:1px solid #ddd;padding:8px;">{{ p.price | currency:'USD' }}</td>
          </tr>
        }
      </tbody>
    </table>
  `
})
export class App {
  items = signal<Product[]>([
    { name: 'Angular', price: 0 },
    { name: 'React', price: 0 },
    { name: 'Vue', price: 0 },
    { name: 'Svelte', price: 0 },
    { name: 'Solid', price: 0 },
    { name: 'Lit', price: 0 }
  ]);
  query = signal('');
  sortKey = signal<'name' | 'price'>('name');
  sortDir = signal<1 | -1>(1); // 1 asc, -1 desc

  view = computed(() => {
    const q = this.query().toLowerCase();
    const dir = this.sortDir();
    const key = this.sortKey();
    return this.items()
      .filter(it => it.name.toLowerCase().includes(q))
      .sort((a, b) => {
        const av: any = (a as any)[key];
        const bv: any = (b as any)[key];
        return av < bv ? -1 * dir : av > bv ? 1 * dir : 0;
      });
  });

  setSort(key: 'name' | 'price') {
    if (this.sortKey() === key) {
      this.toggleDir();
    } else {
      this.sortKey.set(key);
    }
  }

  toggleDir() {
    this.sortDir.set(this.sortDir() === 1 ? -1 : 1);
  }
}

bootstrapApplication(App);
<app-root></app-root>

Run Example »

Example explained

  • computed() view: Derives a filtered/sorted array from signals without mutating the source list.
  • query/sortKey/sortDir: Control the derived view by updating these signals from the UI.
  • @for (p of view(); track p.name): Renders the derived rows; uses a stable key (p.name) for identity.

Note: Avoid heavy work in templates; pre-compute with computed() and loop with @for to keep templates fast.


×

Contact Sales

If you want to use W3Schools services as an educational institution, team or enterprise, send us an e-mail:
sales@w3schools.com

Report Error

If you want to report an error, or if you want to make a suggestion, send us an e-mail:
help@w3schools.com

W3Schools is optimized for learning and training. Examples might be simplified to improve reading and learning. Tutorials, references, and examples are constantly reviewed to avoid errors, but we cannot warrant full correctness of all content. While using W3Schools, you agree to have read and accepted our terms of use, cookie and privacy policy.

Copyright 1999-2025 by Refsnes Data. All Rights Reserved. W3Schools is Powered by W3.CSS.