Why This Change?
Over the last few months, the Angular team has consistently delivered new features across framework releases. One of the latest announcements concerns control flow in templates, which until now relied on structural directives like *ngIf and *ngFor.
The rationale behind this shift is twofold. Many of the patterns we are seeing in Angular today already exist in other JavaScript frameworks, and Angular is working to close that gap—though not without care. Only features that have proven themselves popular with developers and earned the team's seal of approval make the cut. Unifying control flow syntax with other frameworks is part of a broader effort to improve developer experience.
Another key driver is the upcoming support for signal-based components. These will run on a new change detection engine that does not depend on zone.js—a dependency often cited as one of Angular's pain points. Without zone.js, the current structural directives would not function, since they are built on top of it. While refactoring these directives was considered, the risks were deemed too high; such changes could introduce subtle bugs into existing applications and patterns. Other motivations include:
- The new syntax is meant to be flexible and capable of handling multiple template scenarios—
if,else if,else, andswitch—while also decoupling control flow from HTML elements, which should make templates more readable. - The syntax is designed to adapt to a wide range of use cases, whether you need conditional rendering or loops.
Which Options Were Proposed?
Several options were floated during the design phase, but the decision ultimately narrowed down to two frontrunners: #-syntax and @-syntax. Here is how each one looks in practice:
Present syntax
<div *ngIf="users$ | async as users">
{{ users.length }}
</div>
#-syntax
{#if users$ | async; as users}
{{ users.length }}
{/if}
@-syntax
@if (users$ | async; as users) {
{{ users.length }}
}
Present syntax
<ng-container *ngFor="let item of items; let i = index; trackBy: trackByFn">
<li>{{ item.name }}</li>
</ng-container>
<ng-container *ngIf="items.length === 0">
<li>There are no items.</li>
</ng-container>
#-syntax
{#for item of items; track item.name}
<li> {{ item.name }}
{:empty}
<li> There are no items.
{/for}
@-syntax
@for (let item of items; trackBy: item.name) {
<li>{{ item.name }}</li>
@empty {
<li>There are no items.</li>
}
}
The choice was not straightforward. Feedback was gathered from the community, Google Developer Experts, and Googlers who use Angular internally. The @-syntax won the majority of support and is set to appear in Angular 17, which is expected to land within two months.
Another feature that Angular lacks out of the box—though it is achievable with effort—is deferred loading.
The Case for Defer
Efficient application loading calls for a comprehensive strategy around deferred loading that covers both client-side and server-side rendering. While lazy loading through routers and dynamic imports is possible today, the process can quickly become complex and fragile. The new @defer block aims to simplify this by providing a built-in mechanism for postponing work until needed.
The goals are straightforward:
- Ensure the API is both predictable and simple to work with.
- Cut down initial load time by deferring lower-priority content.
- Allow deferred loading to apply to directives and pipes, not just components.
- Make it easy to pair deferred loading with hydration to maximize performance gains.
Key points about @defer:
- It is expressed directly in templates as
@defer, enabling asynchronous dependency downloads. - Developers can swap placeholder content for lazily loaded content usingtwo types of triggers:
onandwhen.
@defer(on viewport) {
<calendar-cmp></calendar-cmp>
}
- The
whentrigger is used for imperative conditions. - The
ontrigger handles predefined events like interaction or timers. - Multiple triggers can be combined within the same block.
- An optional
@placeholderblock can display content before the@deferblock wakes up. - There is also an optional
@loadingblock to show content while dependencies are being fetched. @defersupports an@errorblock for handling load failures.
During server-side rendering, the defer block ignores its triggers and always renders the placeholder—or nothing if no placeholder was provided.
What Lies Ahead
The team is exploring partial hydration as part of Angular's SSR roadmap. The goal here is to hydrating only parts of the rendered content when specific conditions are met, rather than hydrating everything at once. The defer mechanism is a natural fit for this approach and could be extended to support it later.
The RFC for @defer signals a shift toward more predictable and user-friendly performance. It offers a unified solution for deferred loading, with room to grow.
@-syntax
@if (user.isHuman) {
<human-profile [data]="user" />
} @else if (user.isRobot) {
<!-- robot users are rare, so load their profiles lazily -->
@defer {
<robot-profile [data]="user" />
}
} @else {
<p>The profile is unknown!
}
Moving Forward
Introducing a new control flow syntax is another meaningful step in Angular's evolution. It reflects both developer demand and the drive to remain competitive while improving developer experience.
The decision to move forward with @-syntax, informed by feedback from the community and experts, points to a promising direction. With syntax that is distinct from HTML and more flexible in templates, Angular is setting the stage for long-awaited enhancements.
For developers, it would be wise to stay up to date with these changes as Angular 17 approaches. The release is expected to bring substantial updates that could impact real-world projects. The team continues to move quickly, and the community can anticipate further additions. Being part of that momentum is worthwhile.
As always, we invite you to share your perspective. Does this new control flow syntax meet your expectations? Are there aspects you find particularly promising or concerns you would like to raise? Feel free to leave a comment or get in touch—your input matters.
