Angular Control Flow: the complete guide
Tags: angular, frontend, webdev, javascript
Published: 2024-03-06 01:20:59

Angular v17 was released some months ago with a ton of new features, a brand new logo and the new blog [angular.dev](http://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.

---

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 directive ngIf:

```html
@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:

```html
@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:

```html
<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`**:

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

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

```html
@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:

```html
<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:

```html
@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:

```html
<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):

```html
<ul>
  <li *ngFor="let item of itemList; trackBy: itemById">
    {{ item.name }}
  </li>
</ul>
```

```ts
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`**:

```html
<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:

```html
@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**.

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**:

```bash
ng generate @angular/core:control-flow
```

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

And if you really liked it please **[follow me on LinkedIn](https://www.linkedin.com/in/davide-passafaro/)**. 👋