Many screens need to decide what to show: a welcome message for signed-in users and a login prompt for everyone else, a loading spinner while data arrives, an error when something fails. Angular's template control flow handles this with blocks that start with @.
What you will learn
- How to use
@if,@else ifand@else - How to pick one of several views with
@switch - How to avoid repeating a condition with an alias
The @if block
Write the condition in parentheses, and put the content in braces:
@if (isSignedIn()) {
<p>Welcome back!</p>
} @else {
<p>Please sign in.</p>
}
When the condition is true, Angular adds the content to the page. When it is false, the content is removed completely, which is different from only hiding it with CSS.
You can chain conditions:
@if (score() >= 90) {
<p>Excellent</p>
} @else if (score() >= 50) {
<p>Good</p>
} @else {
<p>Keep practising</p>
}
Choosing between several views with @switch
If one value decides between several views, @switch is clearer than a chain of @if:
@switch (status()) {
@case ('loading') { <p>Loading…</p> }
@case ('error') { <p>Something went wrong.</p> }
@default { <p>All good.</p> }
}
A working example
This component shows tasks, a message when there are none, and a status panel:
// src/app/task.model.ts
export interface Task {
id: number;
title: string;
done: boolean;
}
// src/app/task-list/task-list.ts
import { Component, signal } from '@angular/core';
import { Task } from '../task.model';
type Status = 'loading' | 'ready' | 'error';
@Component({
selector: 'app-task-list',
template: `
<h2>Tasks</h2>
@switch (status()) {
@case ('loading') { <p>Loading tasks…</p> }
@case ('error') { <p role="alert">We could not load your tasks.</p> }
@default {
@if (tasks().length > 0) {
<p>You have {{ tasks().length }} task(s).</p>
} @else {
<p>No tasks yet. Enjoy the quiet!</p>
}
}
}
<button type="button" (click)="setStatus('loading')">Loading</button>
<button type="button" (click)="setStatus('error')">Error</button>
<button type="button" (click)="setStatus('ready')">Ready</button>
<button type="button" (click)="addSample()">Add a task</button>
`,
})
export class TaskList {
protected readonly status = signal<Status>('ready');
protected readonly tasks = signal<Task[]>([]);
protected setStatus(next: Status): void {
this.status.set(next);
}
protected addSample(): void {
this.tasks.update((list) => [
...list,
{ id: list.length + 1, title: `Task ${list.length + 1}`, done: false },
]);
}
}
// src/app/app.ts
import { Component } from '@angular/core';
import { TaskList } from './task-list/task-list';
@Component({
imports: [TaskList],
selector: 'app-root',
templateUrl: './app.html',
styleUrl: './app.css',
})
export class App {}
<!-- src/app/app.html -->
<main>
<h1>Control flow</h1>
<app-task-list />
</main>
Click the buttons. The status buttons swap the whole panel, and "Add a task" switches the inner @if from the empty message to the count.
Reusing a condition result with an alias
Sometimes the condition is a value you also want to use inside the block. Add ; as name to keep it:
@if (user(); as currentUser) {
<p>Hello, {{ currentUser.name }}</p>
}
If user() is null, the block is skipped. Inside the block, currentUser is known to be present, so you do not have to check again.
Older syntax
Older Angular code uses *ngIf and *ngSwitch. They still work, but the @ blocks are the current way to write templates, and the CLI can convert old code for you. When you read an older tutorial, translate *ngIf="x" to @if (x) { ... } in your head.
Try it yourself
- Add an
isAdminsignal and show an "Admin tools" heading only for admins. - Add a button that toggles
isAdmin. - Show a different message when there are more than five tasks.
Quick check
- What does Angular do with the content of an
@ifwhose condition is false? - When is
@switchbetter than a chain of@ifblocks? - How do you keep the value of an
@ifcondition for use inside the block?
Next, show many items at once in Rendering lists with @for.