Under The Hood
frontend angular Angular 17+

Angular @for vs *ngFor

Last updated
Prerequisites:
Angular component basics
Understanding of *ngIf / *ngFor structural directives
  • angular
  • control-flow
  • templates
  • change-detection
  • diffing
  • track-by

Read at your depth

The practical view

In Angular 16 and earlier, repeating elements used the structural directive *ngFor="let item of items; trackBy: trackFn". Angular 17 introduced the new control-flow block syntax: @for (item of items; track item.id) { <p>{{ item.name }}</p> } @empty { <p>No items.</p> }. The big practical change is that track is now mandatory — if you omit it, the compiler errors out. The @empty block is new syntax that renders a fallback when the list is empty, replacing an *ngIf="items.length" wrapper. Items are scoped to the loop body with an implicit $index, $first, $last, $even, $odd, $count variables that are still available.

The same idea in other frameworks

react equivalent
items.map((item) => <li key={item.id}>{item.name}</li>)

Legacy vs modern

Legacy *ngFor with optional trackBy vs modern @for with mandatory track

The legacy structural directive forgot keys unless you remembered trackBy; the new control flow demands a track expression at compile time.

before → after
Legacy *ngFor
<ul>
  <li *ngFor="let task of tasks">
    {{ task.title }}
  </li>
</ul>
Modern @for
<ul>
  @for (task of tasks; track task.id) {
    <li>{{ task.title }}</li>
  } @empty {
    <li>No tasks yet.</li>
  }
</ul>

Interview gotchas

Under The Hood — a multi-depth technical interview hub.

Press ⌘ K to search.