New Control Flow — @if, @for, @switch

Native Template Syntax for Conditions and Loops

New Control Flow — @if, @for, @switch

Modern Angular replaced *ngIf and *ngFor with built-in @-syntax that reads like normal control flow.

4 min read Level 2/5 #angular#templates#control-flow
What you'll learn
  • Use @if, @else, and @else if
  • Use @for with mandatory track and @empty
  • Use @switch with @case and @default

Angular 17 introduced built-in control-flow blocks, and v20 made them the recommended default. They are faster, easier to read, and don’t need any imports.

@if / @else if / @else

@if (loading()) {
  <p>Loading…</p>
} @else if (error()) {
  <p class="error">{{ error() }}</p>
} @else {
  <p>{{ data() }}</p>
}

You can chain as many @else if blocks as you like. The blocks read exactly like JS conditionals because they are.

@for — Always With track

@for (todo of todos(); track todo.id) {
  <li [class.done]="todo.done">{{ todo.text }}</li>
} @empty {
  <li>No todos yet.</li>
}

A track expression is required — it tells Angular which DOM nodes correspond to which items so updates can reuse them. Use a stable id when you can; the index works for static lists.

Inside the loop, helpful local variables are available too: $index, $first, $last, $even, $odd.

@for (row of rows(); track row.id; let i = $index, even = $even) {
  <tr [class.alt]="even">{{ i }}. {{ row.name }}</tr>
}

@switch

@switch (status()) {
  @case ('loading') { <p>Loading…</p> }
  @case ('ready')   { <p>Done!</p> }
  @case ('error')   { <p>Try again</p> }
  @default          { <p>Unknown</p> }
}

Why the New Syntax

The old *ngIf and *ngFor were directives, which meant extra generated code, harder type inference, and no built-in “else” or “empty” cases. The new blocks are part of the template compiler, so they’re smaller, faster, and ergonomic enough that the team is encouraging everyone to migrate.

Pipes — Format Values Inline →