Skip to content

Angular 17: *ngFor vs @for Control Flow

Introduction

Angular 17 introduced a fundamentally new approach to template control flow with its built-in blocks (@if, @for, @switch). These replace traditional structural directives like *ngFor, improving performance while offering a more intuitive syntax.

Problem Statement

Developers upgrading to Angular 17 encounter @for syntax in documentation:

html
@for (car of cars; track car) {
  <option [value]="car.value">{{car.viewValue}}</option>
}

This replaces familiar *ngFor:

html
<option *ngFor="let car of cars" [value]="car.value">{{car.viewValue}}</option>

Key questions arise:

  • Is @for a direct replacement for *ngFor?
  • Why was this change introduced?
  • What are concrete advantages of @for?
  • How should existing projects migrate?

Solution: Understanding Angular's New @for

@for is Angular 17's official replacement for *ngFor, offering significant improvements:

1. Mandatory Performance Optimization

The track parameter is now required, forcing developers to specify a unique identifier. This prevents common performance pitfalls in *ngFor where optional trackBy was often omitted:

html
<!-- Angular 17 @for with mandatory tracking -->
@for (car of cars; track car.id) {
  <app-car [car]="car" />
}

The equivalent in *ngFor had no enforced optimization:

html
<!-- Old *ngFor with optional (and often omitted) trackBy -->
<app-car 
  *ngFor="let car of cars" 
  [car]="car" />

2. Built-in Empty State Handling

@for provides dedicated syntax for empty collections via @empty:

html
@for (item of items; track item.id) {
  <div>{{ item.name }}</div>
} @empty {
  <p>No items found</p>  <!-- Rendered when items array is empty -->
}

3. Reduced Boilerplate

  • No imports: Works without importing NgFor (critical for standalone components)
  • Leaner output: Generates ~30% less compiled code than *ngFor
  • Structured flow: Explicit curly braces improve template readability

4. Optimized Change Detection

@for computes minimal DOM operations during updates using the track expression, outperforming *ngFor in most cases. The framework handles optimizations internally.

Feature*ngFor@for
TrackingOptional trackByMandatory track
Empty CollectionManual ngIfBuilt-in @empty
ImportsRequires NgForNo imports
Bundle SizeLargerSmaller

Migration Guide

1. Automatic Conversion

Use Angular CLI to migrate templates:

bash
ng generate @angular/core:control-flow

2. Manual Conversion Steps

Convert this structure:

html
<element *ngFor="let item of items; index as i; trackBy: trackFn">
  {{ item }} {{ i }}
</element>

To:

html
@for (item of items; track trackFn($index, item); let i = $index) {
  <element>
    {{ item }} {{ i }}
  </element>
}

Key Notes

  • track expressions must return primitive values (string/number)
  • $index, $count, and $first/$last provide context variables
  • Migration is backward-compatible (old syntax still works)

When Preference Isn't Enough

While some developers initially prefer *ngFor for its one-line conciseness, @for provides concrete advantages that reduce errors and improve performance. The required track parameter alone prevents common UI update issues caused by improper object tracking.

Conclusion

@for represents Angular's future-proofed approach to iterating content:

  • ✅ Mandatory tracking improves performance
  • ✅ Dedicated @empty block simplifies empty states
  • ✅ Zero dependencies reduce boilerplate
  • ✅ Optimized change detection out-performs *ngFor

Migrate using the Angular CLI command or manual conversion, and reference the official Control Flow Documentation for edge cases. The new syntax offers tangible benefits despite initial familiarization costs.