Latest Angular RFCs: Control Flow & Deferred Loading
At this year's ng-conf, the Angular team unveiled two new RFCs aimed at driving incremental innovation within the framework. The first focuses on novel control flow primitives designed to replace the traditional *ngIf, *ngFor, and *ngSwitch directives. The second, centered around deferred loading, seeks to align with modern hydration strategies and holds substantial promise for reducing startup latency.
This article provides a comprehensive overview of both proposals, concluding with an examination of their implications for Angular developers.
The Rationale Behind New Control Flow Primitives
Currently, templates rely on structural directives to handle control flow, such as *ngIf:
<ng-container *ngIf="userName === 'Chuck Norris'; else someoneElse">
Thanks for participating in this contest. Congrats, you are the winner!
</ng-container>
<ng-template #someoneElse>
Thanks for participating in this contest. We have received your message and will evaluate it during the next couple of decades.
</ng-template>
These conventional directives effectively augment the markup, appearing unobtrusive but introducing several challenges:
- Each directive or the
CommonModulemust be explicitly imported into a Standalone Component. - To avoid extra DOM elements, developers resort to
ng-container, which adds boilerplate. - The
elseclause requires a separate template, which is structurally detached from the*ngIfand merely referenced by it. - The
*switchdirective is particularly cumbersome, requiring three separate directives (ngSwitch,*ngSwitchCase, and*ngSwitchDefault), which hampers type-safety within templates.
These issues, while not critical to application success, are notable. More importantly, as Signal Components pave the way for a Zone-less environment, the current control flow directives are not equipped for this shift, necessitating a revamp. Adhering to the "Boy Scouting Rule" from clean coding, the Angular team has chosen to enhance control flow broadly while preparing for a future without Zone.js.
This initiative has led to the new control flow primitives proposed in the RFC presented at ng-conf 2023.
Understanding the Proposed Control Flow Primitives
With the proposed primitives, the earlier example transforms into:
{#if userName === 'Chuck Norris'}
Thanks for participating in this contest. Congrats, you are the winner!
{:else}
Thanks for participating in this contest. We have received your message and will evaluate it during the next couple of decades.
{/if}
The {#xyz} syntax initiates a block group, {:abc} adds a new block within that group, and {/xyz} concludes it. Each block group inherently contains at least one block using the group's name. Thus, an if group comprises an if block and an else block.
This model clarifies how multiple related blocks fit together. For instance, it's immediately evident that the else block belongs to the if group, unlike the earlier classic example. This clarity is even more apparent when incorporating an else if block:
{#if userName === 'Chuck Norris'}
Thanks for participating in this contest. Congrats, you are the winner!
{:else if isImportantPerson}
Thanks for participating in this contest. We will reach out asap!
{:else}
Thanks for participating in this contest. We have received your message and will evaluate it during the next couple of decades.
{/if}
To align more closely with JavaScript conventions, optional parentheses are available:
{#if (userName === 'Chuck Norris')}
Thanks for participating in this contest. Congrats, you are the winner!
{:else if (isImportantPerson)}
Thanks for participating in this contest. We will reach out asap!
{:else}
Thanks for participating in this contest. We have received your message and will evaluate it during the next couple of decades.
{/if}
This syntax resolves the earlier drawbacks and fulfills the Angular team's objectives:
- It resembles JavaScript while remaining distinct from HTML, offering developers a familiar style that clearly signals control flow.
- Blocks are organized into groups, enabling natural representations of
if-elseif-elseorswitch-case-defaultchains and allowing for cross-block type checking. - In nearly all scenarios, existing directives can be automatically migrated to this new syntax.
Additional Examples
The RFC includes several examples; the following two illustrate its potential. The first demonstrates a switch:
{#switch cond.kind}
{:case x}
X case
{:case y}
Y case
{:case z}
Z case
{:default}
No case matched
{/switch}
The second, also from the RFC, shows a for loop:
{#for item of items; track item.id}
{{ item }}
{:empty}
There were no items in the list.
{/for}
Two notable points emerge: the optional empty block appears when the iterated collection is empty, and the Angular team is considering making the track clause mandatory. Code reviews have shown that omitting trackBy with *ngFor often leads to significant performance issues.
Deferred Loading
The second RFC addresses deferred loading, offering mechanisms to lazily load specific page sections. Prioritizing essential content upfront optimizes loading times.
Building on the control flow RFC's syntax, the following example from the deferred loading RFC demonstrates the intended approach:
{#defer on viewport}
<calendar-cmp />
{/defer}
This defer block is loaded lazily. The on clause specifies the triggering event, with viewport indicating loading when the block scrolls into view. Other triggers include idle, interaction, immediate, timer(x), and hover, where timer's x denotes the delay before loading starts.
A when clause can also define a condition; once it evaluates to true, the deferred content loads:
{#defer when sheduleLoaded}
<calendar-cmp />
{/defer}
Both when and on can be combined. The RFC provides a more detailed example with additional optional blocks:
{#defer on interaction; prefetch on idle}
<calendar-cmp />
{:placeholder}
<img src="placeholder.png" />
{:loading}
<div class="loading">Loading the calendar...</div>
{:error}
<p> Failed to load the calendar </p>
<p><strong>Error:</strong> {{$error.message}}</p>
{/defer}
This includes a prefetch clause, which separates loading from rendering. This allows a deferred part to load earlier based on a different event or condition, ensuring availability when needed. The placeholder block displays on initial page load, the loading block appears during loading, and the error block defines markup for lazy loading failures.
Analysis
To support a Zone-less future, Angular's control flow requires revision. Following the "Boy Scouting Rule," the Angular team is seizing this opportunity to address drawbacks in current control flow directives, resulting in new primitives that mirror JavaScript syntax.
Like all Angular features, these new primitives remain optional. Existing directives such as *ngIf and *ngFor will persist, supported by both traditional and Signal components when Zone.js is in use. Additionally, an automated migration will handle the vast majority of cases.
The deferred loading proposal builds directly on the control flow syntax. It enables lazy loading of page segments, enhancing startup performance and bringing the framework in line with popular hydration approaches in the JavaScript ecosystem.
Further Insights on Modern Angular?
Our free eBook covers Standalone Components in depth:
- The conceptual framework behind Standalone Components
- Migration strategies and compatibility with existing codebases
- Standalone Components with the router and lazy loading
- Standalone Components and Web Components
- Standalone Components with DI and NGRX
Access the eBook here:
You can also download it directly.

