Why I Like the “Built-In Control Flow” Idea in Angular

Why improvements in this area will be extremely beneficial for every developer.

🪄 OZ 🎩
3 min readJun 15, 2023
View of the Grand Canal with the Santa Maria della Salute and the Dogana, Bernardo Bellotto, 1743

A new RFC in Angular, “Built-In Control Flow” excited me (and many others), but not everyone. Some don’t like the change per se, some don’t like that some structural directives will be deprecated, some don’t like how the new syntax looks, and so on. Here I’ll explain why I like this new idea.

Structural directives should not be directives

They are simply not related to the component they are applied to.
Look at this code:

<ul>
<li *ngFor="let item of items">
{{item.label}}
</li>
</ul>

Here ngFor is not an attribute of a rendered element li — it defines if that element should be rendered at all.

Now let’s complicate our code a bit, and only render items that have non-empty label:

<ul>
<ng-container *ngFor="let item of items">
<li *ngIf="!!item.label">
{{item.label}}
</li>
</ng-container>
</ul>

Because we can’t apply more than one structural directive to an element, we moved ngFor away from li and it didn’t affect how li looks or behaves.

If we can move it out, then it was not an “attribute” of li, it was a control flow mechanism, implemented as a directive — in the second example it’s still a directive, applied to the artificial element ng-container. It should be implemented as a control flow mechanism and should not look like an attribute of some element.

It makes code more compact

Even simple “if-else” in Angular templates look awkward right now. Cases like “if-else if-else” quickly become confusing and too large:

<ng-container *ngIf="item.type==='email'; else nonEmail">
<a href="mailto:{{item.ref}}">{{item.ref}}</a>
</ng-container>

<ng-template #nonEmail>
<ng-container *ngIf="!!item.label; else noLabel">
<a href="{{item.ref}}">{{item.label}}</a>
</ng-container>

<ng-template #noLabel>
<div>Wrong link</div>
</ng-template>
</ng-template>

Compare it with:

{#if item.type==='email'}
<a href="mailto:{{item.ref}}">{{item.ref}}</a>
{:elseif !!item.label }
<a href="{{item.ref}}">{{item.label}}</a>
{:else}
<div>Wrong link</div>
{/if}

More powerful than user-land directives

Because of the “built-in” nature of the new control flow, the framework can make some “preparations” and optimizations during the “build” phase. It opens the door to things that weren’t possible before (or were quite tricky to implement). And a great example is #defer (see “RFC: Deferred Loading”).

Removes the micro-syntax limitations

We’ll get much better type-checking and type-narrowing.

If you haven’t heard about micro-syntax: every time you write,

<li *ngFor="let item of items; index as i; trackBy: trackByFn">{{item}}</li>

Angular translates it into the long form of ngFor (that, I’m sure, very few developers use on a regular basis):

<ng-template ngFor 
let-item
[ngForOf]="items"
let-i="index"
[ngForTrackBy]="trackByFn"
>
<li>{{item}}</li>
</ng-template>

So, developers had to create micro-syntax to make that shorthand possible, to don’t make you use the long form with its awkward API. Built-in control flow removes that awkwardness and gives better type-narrowing.

Third-party structural directives will remain

There are well-known and quite handy structural directives like rxFor, ngrxLet — they will not be deprecated or affected in any way. The concept of structural directives itself will not be deprecated: you can still create and use them. They can not get all the power that built-in control flow has, but their existing features are not going to go anywhere.

I understand the reasoning of the Angular team why they don’t want to make the new control flow syntax extensible — once they’ll provide an API, they are limited by the given API. Such API would have a chance to be beneficial for the third-party directives, but the unhindered evolution of such a defining feature of the framework is more important.

It’s a chance to greatly improve Angular

Control flow is an essential feature, one of the core features of the framework. Any improvements in this area will be extremely beneficial for every developer.

Built-in control flow removes control flow from “user-land code” and gives full control to the framework — it’s not just some structural directives anymore, so Angular can do more things in a more optimal way. I believe that #defer is just the first example of many nice features we’ll get out of this idea.

--

--