Update 2023–10–07: The syntax shown here has been adjusted to reflect the final form with @.
Lately, Angular's pace of change has been a topic of debate—some see it as a steady platform, others as a project that had stalled. That narrative is shifting quickly. The framework is accelerating, and the direction it's taking is becoming clearer.
At the core of this push is a move to replace the traditional structural directives—NgIf, NgForOf, and NgSwitch—with a fresh built-in syntax that feels more like macros. The old concept isn't going away entirely, but the new pattern is intended to become the standard practice. The transition is designed to be backwards compatible, so you'll be able to mix both styles across different files during the transition period.
The motivation here is to make templates more legible and to align Angular with conventions that feel more familiar to developers coming from other frontend ecosystems.
A New Take on Control Flow
The team behind Angular has outlined a plan to phase out the familiar structural directives in favor of something that behaves more like an embedded language. While the underlying idea of structural directives remains relevant, this improved syntax is set to become the default recommendation. Because the new format is backwards compatible, both approaches can coexist, even within the same project, for quite a while.
The project's goals include improving readability and lowering the barrier for adoption across the larger frontend community.
The Old Way
To boost performance when handling large lists with *ngFor, developers have relied on the trackByFunction. In the current implementation, this requires a function that tells Angular how to identify each item. The new syntax removes that overhead by allowing you to pass property names directly.
Instead of relying on object identity, Angular will use a specified property like id to detect changes within the collection. This streamlines updates, especially when each item has its own unique identifier.
trackByFunction(index, item) {
return item.id;
}
<div *ngFor="let item of items; index as idx; trackBy: trackByFunction">
Item #{{ idx }}: {{ item.name }}
</div>
The New Way
@for (item of items; track item.id; let idx = $index, let e = $even)
{
Item #{{ idx }}: {{ item.name }}
}
This updated approach retains access to special variables within the loop's scope, including $index, $first, $last, $even, and $odd. While these can be used directly, you also have the option to rename them via the 'let' clause. Notice how the syntax now pushes you to provide a 'track' expression, which helps Angular's diffing process run more efficiently.
@for (item of items; track item.id)
{
{{ item }}
}
@empty
{
There were no items in the list.
}
A key feature in this revamp is the 'empty' block, which lets you define what gets rendered when the source list is empty. On the technical side, the customizability of IterableDiffers is being set aside in favor of a more performant, built-in diffing algorithm that Angular will manage internally.
Making If-Else More Intuitive
The old pattern of pairing ng-container with ng-template to use *ngIf has been a common source of confusion. The new control flow was designed with this feedback in mind, aiming to make conditional rendering more straightforward.
Classic Approach (Conceptually)
<ng-container *ngIf="cond.expr; else elseBlock">
Main case was true!
</ng-container>
<ng-template #elseBlock>
<ng-container *ngIf="other.expr; else finalElseBlock">
Extra case was true!
</ng-container>
</ng-template>
<ng-template #finalElseBlock>
False case!
</ng-template>
The Modern Take
@if (cond.expr)
{
Main case was true!
}
@else if (other.expr)
{
Extra case was true!
}
@else
{
False case!
}
Introducing the Switch Block
Replacing *ngSwitch is a dedicated 'switch' block. This iteration is expected to offer tangible improvements, particularly with stronger template type-checking. It also removes the necessity for wrapper elements just to hold the condition expression. The structure of the new block is shown below:
@switch (condition)
{
@case (caseA)
{
Case A.
}
@case (caseB)
{
Case B.
}
@default
{
Default case.
}
}
How Do We Get There?
Moving your codebase over is expected to be something of a gentle ride. The Angular team has committed to building an automated migration schematic that will handle a large portion of the conversion for you. Still, you'll want to double-check any pieces of your application that rely on a custom diffing algorithm, since these could behave differently with the new 'for' directive.
Looking Beyond the Horizon
The proposals for this syntax don't stop at the basics. The Angular team has stated goals of expanding support to other flavors of JS loops, such as handling async iteration and for-in patterns. Other ideas on the roadmap include built-in support for virtual scrolling and destructuring within the template.
If you want to see what this foundational change enables, there's a separate RFC that showcases a feature built directly on top of it. The proposal is for Deferred Loading.
Common Questions, Answered
Here’s a rundown of the questions that are likely on many developers’ minds when they hear about this new syntax:
What happens to existing structural directives? The current set,
NgIfand friends, remains functional. That said, Angular is going to push hard for adoption of the new approach.Is the structural directive concept going away? Not at all. It stays as a core part of the framework.
Will my editor recognize the new syntax? Yes. The Angular Language Service is being updated to provide highlighting for the keywords and expressions found inside the new blocks.
Will this change what my queries return? No, the results of your queries are unaffected.
Do I need to import anything? No. Since this is part of the template language itself, it will be available across all components without any extra setup.
Is there any performance benefit? Possibly. Minor gains might show up, especially when it comes to
forloops and the diffing process.Can I create custom blocks or attach directives? As it stands, no. The new syntax does not offer a way for libraries to create their own block types, and directives cannot be placed on these blocks either.
Your Take?
This adjustment to the syntax will undoubtedly reshape how you build templates. Do you see it as a necessary evolution? Will it noticeably improve your developer experience, and could it attract people who have been leaning towards other frameworks?
For me, the migration period is the biggest worry. We have a major shift in the reactive model with Signals plus this new Template Syntax arriving. It's all starting to form a bigger narrative that we haven't seen the full picture of yet. Angular developers will, for a while, have plenty of ways to manage reactivity: RxJS, Signals, Promises, Change Detection (zone.js) — and now two distinct ways to structure templates.
A complete move to this new direction isn't something you can rush, and it will take considerable time. The real question for me is whether the long-term payoff justifies all the friction we'll have to deal with in the meantime.
I'd love to hear your perspective, so let's get a conversation going!
Enjoyed the read?
If so, you might be interested in what I'm doing on Twitter. I run live Twitter Spaces dedicated to Angular, featuring GDEs & other experts. Tune in to ask questions or check out the replay clips afterward.
If that sounds interesting, give me a follow on Twitter @DanielGlejzner — it would mean a lot. Thanks!
Looks like my coffee mug is empty again…
…if you'd like to fix that, here's the link: https://ko-fi.com/danielglejzner








