Angular's Built-in Control Flow: @if, @for and @switch
Angular's structural directives (*ngIf, *ngFor, *ngSwitch) have served us well, but the
built-in block control flow introduced in v17 is cleaner, faster, and easier to read. Here's a
quick tour of how I use it day to day.
The new syntax
Instead of an *ngIf directive on an element, you write a block directly in the template:
@if (user(); as u) {
<p>Welcome back, {{ u.name }}</p>
} @else {
<p>Please sign in.</p>
}
Loops read the same way, and track is now required — which nudges everyone toward the
performance win that trackBy used to make optional:
@for (item of items(); track item.id) {
<li>{{ item.label }}</li>
} @empty {
<li>Nothing here yet.</li>
}
Why it matters
- No imports. Control flow is part of the compiler, so there's no
CommonModuleorNgIf/NgForto pull into standalone components. - Built-in
@empty. The empty-list case is first-class instead of a second@if. - Faster rendering. The new
@forships a more efficient diffing algorithm, and mandatorytrackavoids needless DOM churn.
Migrating
The Angular CLI ships a schematic that rewrites the old directives for you:
ng generate @angular/core:control-flow
Run it, review the diff, and commit. On a large app I migrated incrementally, one feature module at a time, with zero runtime regressions.
Rule of thumb: reach for block control flow in every new template. It's less code, and the required
trackkeeps lists fast by default.
That's the whole pitch — smaller templates, fewer imports, and performance you get for free.