Angular Control Flow: the complete guide

Davide Passafaro
4 min readNov 15, 2023

--

Angular v17 was released some days ago with a ton of new features, a brand new logo and the new blog angular.dev.

In this article I will dive into the new control flow, which will make you forget about directives like ngIf, ngSwitch and ngFor thanks to a new syntax to write if, if/else and switch statements and the for loop in our template.

⚠️ FIRST ALERT: the new control flow is still in developer preview ⚠️

Let’s start our journey learning about the most fundamental condition: @if.

@if condition

In order to show a block of template based on a condition it has always been used the ngIf directive:

@Component({
standalone: true,
template: `<div *ngIf="condition"> ... </div>`,
imports: [NgIf]
})
export class MyComponent {}

This simple directive has indeed some cons:

  • The syntax is not intuitive and to much framework related;
  • It’s not possible to apply it to groups of elements, to do so you need to wrap the elements inside a container or ng-template/ng-container;
  • You need to import CommonModule or the directive NgIf in your modules.

The new @if condition lets you obtain the same result:

@if (condition) {
*your content*
}

@if offers you a cleaner syntax, the conditional blocks stand out immediately while reading the template, allowing to wrap groups of elements inside curly brackets an most of all: no more modules or directives to import.

@else and @else/if

if/else condition were always a pain in Angular:

<div *ngIf="condition; else otherTemplate">
*your content*
</div>

<ng-template #otherTemplate>
*your other content*
</ng-template>

The need of an ng-template combined with a template variable to provide at ngIf directive a backup block was always tricky and not very immediate.

And here is where @if helps the most, in fact it can be extended using @else:

@if (condition) {
*your content*
} @else {
*your other content*
}

But it’s not just that, @if can be extended even more using @else/if:

@if (condition) {
*your content*
} @else if (secondCondition) {
*your second content*
} @else {
*your other content*
}

Thanks to this you can create templates with complex conditions writing clean and intuitive code. Easy to read, easy to maintain.

Now, let’s proceed with @switch.

@switch condition

Up until today the ngSwitch directive was used to show a certain block of template in a list based on a switch condition:

<div [ngSwitch]="condition">
<div *ngSwitchCase="value1">*content block value1*</div>
<div *ngSwitchCase="value2">*content block value2*</div>
<div *ngSwitchDefault>*default content*</div>
</div>

Similarly to @if there is now a brand new @switch condition:

@switch(condition) { 
@case ('value1') {
*content block value1*
} @case ('value2') {
*content block value2*
} @default {
*default content*
}
}

In this case you can obtain a cleaner syntax and no more imports.

Here we are at the last topic: the @for loop.

@for loop

Showing a list of blocks in a template to represent a list of elements is a key concept of a lot of frameworks, and in Angular this task could be accomplished using the ngFor directive:

<ul>
<li *ngFor="let item of itemList">
{{ item.name }}
</li>
</ul>

This directive allows you to create a customized block for each element of the list, thanks to the ability to use the information of the single element and some local variables provided by the directive itself:

  • index: number: index of the element in the list;
  • count: number: the length of the list;
  • first: boolean: true if the element is the first of the list;
  • last: boolean: true if the element is the last of the list;
  • even: boolean: true if the element index is even;
  • odd: boolean: true if the element index is odd.

Also, the ngFor directive accepts a function called trackBy as optional parameter, used to provide to Angular a unique identifier for each element of the list (like an id):

<ul>
<li *ngFor="let item of itemList; trackBy: itemById">
{{ item.name }}
</li>
</ul>
itemById(index, item) {
return item.id;
}

Thanks to this Angular is able to optimize performances when the list is replaced or an element is modified.

So, in order to replace the ngFor directive, the new control flow provides @for:

<ul>
@for (item of itemList; track item.id; let idx = $index, e = $even) {
<li>{{ item.name }}</li>
}
</ul>

This new syntax brings some important changes:

  • Performance improvements up to 90% as compared to ngFor, thanks to the new algorithm used to implement @for;
  • The trackBy function is replaced by the track property that requires to provide an expression, easier to write and read than an entire function;
  • The track property is required so that we are not going to forget it, as it happens commonly with the trackBy function;
  • The local variables has now the prefix $ (e.g.: $index).

Furthermore you can obtain also a cleaner syntax and no more imports.

@empty block

Finally the new @for loop brings a really useful @placeholder condition that allows to show a block if the list is empty:

@for (item of itemList; track item.id) {
<div>{{ item.name }}</div>
} @placeholder {
<div>No items available</div>
}

Awesome indeed 🤩

Thanks for reading so far 🙏

As you could see the new control flow offers a fresh new way to write complex templates condition in our Angular applications, which brings performance improvements and more maintainable code.

⚠️ SECOND ALERT: the new control flow is still in developer preview ⚠️

If you want to try it already I suggest you to test it on your existing projects. You just have to ng update your app and use the migration command offered by the Angular CLI:

ng generate @angular/core:control-flow

Thanks for reading, I’d like to have your feedback so please leave a comment, clap or follow. 👏

And if you really liked it please follow me on LinkedIn. 👋

--

--

Davide Passafaro

Senior Frontend Engineer 💻📱 | Tech Speaker 🎤 | Angular Rome Community Manager 📣