Control Flow Is Not Just Cleaner Syntax

@if, @for, and @switch are most useful when they make UI states and list identity explicit. Built-in control flow became stable in Angular v18 (May 2024) after the RFC #50719 closed. In production, the win is not syntax, it is what the new blocks force the team to name.

Cleaner is not enough

I like the new control flow because it reduces template gymnastics, but 'cleaner syntax' is not a strong enough reason to open a broad migration. In a mature app, I want the change to expose a real UI decision: loading, error, empty, ready, identity, or a finite state.

That framing keeps the migration useful. A touched template can become easier to review without turning the whole codebase into a syntax campaign.

What each block should prove

BlockGood production useReview question
@ifReplace scattered ng-template branchesDid the main path and fallback become easier to read?
@forRender dynamic lists with explicit identityIs track based on a stable id instead of convenience?
@emptyMake empty states visibleDid the previous UI silently render nothing?
@switchRepresent mutually exclusive UI statesIs the state model finite and typed?
@deferDelay genuinely heavy UIWhich JavaScript leaves the initial path?

The list identity decision

@for forcing track is not busywork. It makes the team decide how the DOM should relate to data. That matters for rows with inputs, focus, animations, and repeated components with internal state.

If the backend gives you a stable id, use it. If it does not, ask whether the UI is hiding a modeling problem. Falling back to object identity or index can be acceptable for static lists, but it should not be the default for dynamic product data.

I would push back on track $index for a sortable or filterable product list. It can preserve the wrong row state when the order changes, which is exactly the kind of bug a syntax migration should not introduce.

Bad dynamic list identityhtml
@for (order of filteredOrders(); track $index) {
  <app-order-row [order]="order" />
}
Template state with explicit identityhtml
@switch (ordersState()) {
  @case ('loading') {
    <app-orders-skeleton />
  }
  @case ('empty') {
    <app-empty-orders />
  }
  @case ('ready') {
    @for (order of orders(); track order.id) {
      <app-order-row [order]="order" />
    }
  }
}

A migration I would accept

I would accept a control flow PR when the team is already touching the screen and the diff improves one of three things: a clearer state model, a correct track decision, or a useful empty/fallback state.

I would push back on a repo-wide PR that only changes syntax. The code may compile, but the review cost is real and the product learned nothing.

Reusable artifact

Control flow review checklist

  • Migrate touched templates, not the whole repo by default.
  • Use @for with stable identity for dynamic data.
  • Add @empty when the old UI silently rendered nothing.
  • Use @switch for finite UI states instead of stacked conditions.
  • Use @defer only when it changes loading cost or user experience.

Sources checked

Modern Angular Playbook

This article is one play.

The Modern Angular Playbook collects the diagnostic, the adoption matrix, eleven plays, and the 30-day plan. Free, in English and Portuguese.

You get both PDFs by email, through the Dojo IA list.

Open playbook →

André Ramosavailable for remote roles, UTC−3Get in touch →

Related

Guide · 7 min

Standalone Without Turning NgModules Into a Villain

A practical rule for using standalone components, route providers, and remaining NgModules in mature Angular applications.

Read article →

Guide · 10 min

A 30-Day Modernization Plan for Mature Angular Apps

A modernization plan for teams that need audit, low-risk changes, isolated spikes, and decisions that can survive code review.

Read article →

Note · 6 min

How I Would Adopt Signal Forms Now That They Are Stable in Angular 22

A production adoption filter for Signal Forms now that they are stable in Angular 22: use them for new forms, keep reactive forms where they already work.

Read article →