Control flow: @if, @for, @switch
Control flow blocks decide which part of a template to show and how many times to repeat it. They start with the @ sign and are built into the template language, so you do not need to import anything.
@if
The block is rendered only when the condition is true:
<!-- src/app/todo-list/todo-list.html -->
@if (todos().length === 0) {
<p>No tasks yet.</p>
}While the list is empty, the page shows "No tasks yet.". As soon as a task appears, the paragraph is removed from the markup completely.
@else and @else if
<!-- src/app/todo-list/todo-list.html -->
@if (left() === 0) {
<p>All done!</p>
} @else if (left() === 1) {
<p>One task left.</p>
} @else {
<p>Tasks left: {{ left() }}</p>
}The first branch with a true condition runs, just like an ordinary JavaScript if.
@for
@for repeats a block for every array element. The track expression is required: it tells Angular which field identifies an element.
<!-- src/app/todo-list/todo-list.html -->
<ul>
@for (todo of todos(); track todo.id) {
<li [class.done]="todo.done">{{ todo.title }}</li>
}
</ul>Each task becomes a list item, and completed ones get the done class. Thanks to track, when a task is added Angular creates one new li instead of redrawing the whole list.
@empty and helper variables
The @empty block is shown when the array is empty. Inside @for the variables $index, $count, $first, $last, $even and $odd are available:
<!-- src/app/todo-list/todo-list.html -->
<ul>
@for (todo of todos(); track todo.id) {
<li>{{ $index + 1 }}. {{ todo.title }}</li>
} @empty {
<li>No tasks yet.</li>
}
</ul>Tasks are shown with numbers: "1. Buy bread", "2. Call Oleg".
@switch
Useful when you pick one option out of several by value. Add a filter to the class:
// src/app/todo-list/todo-list.ts
filter = signal<'all' | 'active' | 'done'>('all');<!-- src/app/todo-list/todo-list.html -->
@switch (filter()) {
@case ('active') {
<h3>Active tasks</h3>
}
@case ('done') {
<h3>Completed tasks</h3>
}
@default {
<h3>All tasks</h3>
}
}The heading changes together with the filter signal. The @default block runs when no @case matches.
The old syntax
Before Angular 17, the directives *ngIf, *ngFor and [ngSwitch] were used instead of blocks. They still work, but in new projects write @if, @for and @switch — they need no imports and are faster.
Common mistake
Writing track $index for a list that changes: when a task is removed from the middle, the indexes shift, and Angular redraws every element after it — typed text and focus are lost. Track elements by a unique field, for example track todo.id.