Open-Source Wikis

/

Angular

/

Features

/

Control flow blocks

angular/angular

Control flow blocks

The @if, @for, @switch, @let template syntax that replaces *ngIf, *ngFor, *ngSwitch. Introduced in v17 and now the documented default. Implemented in packages/compiler/src/render3/view/template.ts (parser) and packages/core/src/render3/instructions/control_flow.ts (runtime).

What it looks like

@if (user(); as currentUser) {
<p>Welcome {{ currentUser.name }}</p>
} @else {
<p>Please sign in</p>
} @for (item of items(); track item.id) {
<li>{{ item.title }}</li>
} @empty {
<li>No items</li>
} @switch (status()) { @case ('loading') { <spinner /> } @case ('ready') {
<my-data /> } @default { <error-state /> } } @let displayName = user()?.fullName
?? 'Anonymous';

Why it exists

The structural-directive form (*ngIf, *ngFor, *ngSwitch) had limitations:

  • Type narrowing was awkward. *ngIf="user as u" couldn't always carry the type through.
  • *ngFor's trackBy required a method on the component, not a template-local expression.
  • *ngSwitch required *ngSwitchCase inside, which felt redundant.
  • Composing structural directives on the same element required <ng-container> gymnastics.

Block syntax fixes all of these. Type narrowing works through the @if (cond; as x) form. track is a first-class block field. @switch is a single block.

Compiler pipeline

graph LR
  Source["@if / @for / @switch / @let<br/>in template source"] --> Parser["template.ts parser"]
  Parser --> AST["IfBlock / ForLoopBlock / SwitchBlock / DeclareLetBlock"]
  AST --> IR["Render3 view IR<br/>(IfBlockOp, RepeaterOp, ...)"]
  IR --> Codegen["Output IR<br/>(ɵɵconditional, ɵɵrepeater, ...)"]
  Codegen --> Runtime["Runtime instructions"]

The parser produces dedicated AST node types in packages/compiler/src/render3/r3_ast.ts:

  • IfBlock, IfBlockBranch@if / @else if / @else.
  • ForLoopBlock, ForLoopBlockEmpty@for and its @empty companion.
  • SwitchBlock, SwitchBlockCase@switch / @case / @default.
  • DeferredBlock and friends — the @defer family.
  • DeclareLetBlock@let.

The view compiler under packages/compiler/src/render3/view/template.ts lowers them to IR.

Runtime instructions

Three instruction families in packages/core/src/render3/instructions/:

@for's track expression is required (review enforces it); the reconciler uses it to identify items and decide whether to reuse, move, or destroy a view.

Type checking

The TCB generator in compiler-cli's typecheck/ subsystem was extended to model block semantics:

  • @if-narrowed expressions get a refined type in the matching branch.
  • @for introduces a loop-local variable with the array element type.
  • @let introduces a constant whose type comes from the right-hand-side expression.

strictTemplates mode catches misuse (e.g., a @for track referencing a property the item doesn't have).

Migration

packages/core/schematics/migrations/control-flow-migration/ converts existing *ngIf/*ngFor/*ngSwitch usage to the block syntax automatically. It's run via ng update @angular/core --migrate-only=control-flow-migration.

The migration handles edge cases:

  • Removing redundant <ng-container> wrappers.
  • Preserving structural-directive else templates as @else branches.
  • Translating trackBy: methodRef to track methodRef($index, $item).

Where to start when modifying

  • A new block type: extend the parser AST in packages/compiler/src/render3/r3_ast.ts, the parser in template.ts, the IR ops, the codegen, the runtime instruction, and the type-check generator. Six places at once — block work is a coordinated change.
  • A new field on an existing block (e.g., @for's track): same surface but smaller footprint.
  • A type-check refinement for an existing block: only the TCB generator and tests.

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Control flow blocks – Angular wiki | Factory