One of the most significant enhancements to the Angular framework was the introduction of the new built-in syntax for control flow, originally made available in Angular 17.

This syntax helps with one of the most common tasks that we need to do as developers: show or hide elements on a page depending on a logical condition.

In the past, we used to do that with the traditional structural directive *ngIf.

But now we have a much better alternative to ngIf: the new @if syntax, which is part of the new template control flow features.

We will compare the two syntaxes, and explain why the new @if syntax is better and how to automatically migrate to it.

In this guide, we'll delve into why the new @if is a game-changer, providing a more intuitive and ergonomic alternative to *ngIf.

We'll also talk about a common anti-pattern that developers often encounter, which has to do with the use of @if in conjunction with the async pipe.

Ready to level up your Angular skills? Let's dive in!

Table of Contents

This post covers the following topics:

  • Why a new control flow syntax?
  • What is Angular @if?
  • The @if syntax
  • The @if else syntax
  • The @if else if else syntax
  • Why don't we need to import @if?
  • Why don't we need to use the * syntax anymore?
  • Why is the new @if syntax better than ngIf?
  • How to easily migrate to the new @if syntax?
  • How does @if compare to hiding elements using CSS?
  • Multiple nested @if async pipe anti-pattern
  • Summary

Note: if you are looking to learn about the old *ngIf syntax instead, check out my other guide here: Angular *ngIf: Complete Guide

And if you are looking for the new @switch which is also used for conditional rendering, check out this guide: Angular @switch: Complete Guide.

Why a new control flow syntax?

According to Minko Gechev in the post where @if was introduced
this new built-in control flow syntax enables:

  • More ergonomic and much less verbose syntax that is closer to JavaScript, thus more intuitive requiring fewer documentation lookups.

  • Template syntax like @if is automatically available in your templates without any additional imports

  • Better type checking thanks to more optimal type narrowing

  • It’s a concept that primarily exists at build-time, which reduces the runtime footprint, helping you drop your bundle size and improve your Core Web vitals.

  • also, the control flow syntax will make it easier to implement signal-based change detection in the future

You can read all about @if and the new template syntax in the official docs: Angular Control Flow.

What is Angular @if?

@if is to Angular what if is to JavaScript.

@if is a new template syntax for conditionally rendering elements in Angular templates. It's a built-in control flow syntax that allows you to conditionally show or hide page elements depending on a logical boolean condition.

Unlike the traditional *ngIf structural directive, you don't need to import @if in standalone components, it's automatically available everywhere.

It's a more concise and readable syntax, that also supports else if and else conditions, unlike the previous *ngIf syntax.

Let's explore this syntax in more detail.

The @if syntax

Here is an initial example of how to use this syntax:

@Component({
  template: `
    @if (showHello) {
    <h2>Hello</h2>
    }
  `,
})
class Test {
  showHello: boolean = true;
}

The above code will only render the <h2>Hello</h2> element if the showHello property is true.

Do you see how this syntax is so similar to JavaScript's if statement?

if (showHello) {
  return `<h2>Hello</h2>`;
}

It's almost identical!

The @if else syntax

The @if syntax also supports else conditions, unlike the previous *ngIf syntax.

Let's see an example:

@Component({
  template: `
    @if (showHello) {
    <h2>Hello</h2>
    } 
    @else {
    <h2>Goodbye</h2>
    }
  `,
})
class Test {
  showHello: boolean = true;
}

The code above will render:

  • the <h2>Hello</h2> element if the showHello property is true
  • the <h2>Goodbye</h2> element if the showHello property is false

This is visually much more like the if-else syntax in JavaScript.

The @if else if else syntax

Angular is not yet done. They also added support for else if conditions. This is something that was not possible with the previous *ngIf syntax.

Let's see an example:

@Component({
  template: `
    @if (showHello) {
      <h2>Hello</h2>
    } 
    @else if (showGoodbye) {
      <h2>Goodbye</h2>
    } 
    @else {
      <h2>See you later</h2>
    }
  `,
})
class Test {
  showHello: boolean = true;
  showGoodbye: boolean = false;
}

The code above will:

  • render the <h2>Hello</h2> element if the showHello property is true
  • the <h2>Goodbye</h2> element if the showGoodbye property is true and showHello is false
  • the <h2>See you later</h2> element if both the showHello and showGoodbye properties are false.

Again, this is so close to what we would write with just plain Javascript.

Here is a video from our YouTube channel demonstrating the new @if syntax in action:

Why don't we need to import @if?

Notice that we don't have to import the @if directive from @angular/common into our component templates anymore.

This is because the @if syntax is part of the template engine itself, and it is not a directive.

The new @if is built-in directly into the template engine, so it's automatically available everywhere.

It's just like the {{variable}} interpolation syntax or the i18n syntax - you don't need to import anything in order to use it.

Why don't we need to use the * syntax anymore?

The * syntax was only used because ngIf was a structural directive. The * syntax was just syntactical sugar to make it easier for developers to use structural directives.

You can read more about how the old * syntax works under the hood here.

The bottom line is that with @if we don't need to use the * syntax anymore, because @if is not a structural directive.

Why is the new @if syntax better than ngIf?

Let's quickly summarize the main reasons why @if is better than *ngIf:

  • less verbose, more intuitive
  • no need for imports
  • supports else if and else conditions
  • no runtime overhead
  • will make the future evolution of the framework easier

How to easily migrate to the new @if syntax?

If you are using older versions of Angular, you can migrate to the new @if syntax using the Angular CLI.

The Angular CLI has an automated migration for upgrading all your code to the new @if syntax:

ng generate @angular/core:control-flow

This command will migrate all the *ngIf directives in your project to the new syntax, not only for @if but also for the @for and @switch syntax.

How does @if compare to hiding elements using just plain CSS?

In HTML, we can hide elements by manipulating their display and visibility properties:

  • if we set the display of an element to none the document is not visible in the document
  • when the visibility of an element is set to hidden, the element is not shown.

These all may look similar because they give the same results, but they are not the same.

In both cases, the hidden element still exists in the DOM, while with @if the hidden element doesn't exist on the DOM at all.

Multiple nested @if async pipe anti-pattern

Remember in the beginning we talked about an @if anti-pattern?

If you use RxJs in your code, I think this is well worth mentioning it because you will see it a lot, and it's directly related to @if.

This has to do with the usage of the @if syntax not really to show or hide an element, but just to access the values of an observable using the async pipe.

This anti-pattern is sometimes known as "async pipe chaining" or "pyramid of doom".

Here is an example of this @if anti-pattern:

@if (user$ | async; as user) {
    ....
    @if (course$ | async; as course) {
        ....
        @if (lessons$ | async; as lesson) {
            ....
        }
    }
}

As you can see, we are using async pipe to unwrap the values of different Observables and assign them to a local template variable.

Then we are using the local template variable in the next @if statement.

We are repeating the use of @if and the async pipe, at multiple levels of the page, just for the purpose of accessing data, and nothing more.

This practice makes the code hard to read and maintain if several nesting levels are needed.

Besides, it makes the code harder to refactor, if you are doing this locally in several parts of the page instead of the whole page.

A good way to avoid this is to refactor your component so that it provides one single data$ observable, containing all the data that the page needs.

Start by defining one interface that contains all the data that the page needs:

interface PageData {
    user: User;
    course: Course;
    lessons: Lesson[];
}

Then, define a single data$ observable that contains all the data that the page needs:

@Component
export class Component implements OnInit {
    
  private data$: Observable<PageData>;

  ngOnInit() {
    
    const user$ = // ... initialize user$ observable

    const course$  = // ... initialize course$ observable

    const lessons$ = // ... initialize lessons$ observable    
    
    this.data$ = combineLatest([user$, course$, lessons$])
      .pipe(
        map(([user, course, lessons]) => {
          return {
                user, 
                course, 
                lessons
            }
        })
    );
  }
    
}

The operator combineLatest is just one way to create this combined Observable.

Finally, use @if in combination with the async pipe to unwrap the data$ observable in the template:

@if (data$ | async; as data) {
    ....
    {{data.course}}

    {{data.user}}

    {{data.lessons}}
}

As you can see, this removes all the unnecessary @if nesting and makes the code much more readable and maintainable.

If you apply this at the beginning of the template, you will have all your data easily available everywhere.

Looking to learn more?

To get notified when new similar posts like this are out, I invite you to subscribe to our newsletter:

You will also get up to date news on the Angular ecosystem.

And if you want a deep dive into all the features of Angular core like @if, have a look at the Angular Core Deep Dive Course:

Summary

In this guide, we've explored the new @if syntax in Angular. We've seen how it is a more concise and readable syntax, with no imports needed, and that also supports else if and else conditions, unlike the previous *ngIf syntax.

With @if we don't need to use the NgIf directive anymore, and we don't have to use the sometimes qwirky structural * syntax.

The @if syntax is a powerful addition to the Angular toolkit. So go and try this new control flow syntax!

Try the Angular CLI control flow migration on your application, and let me know in the comments below how it went.

If you have any questions or comments, let me know as well. I'll be happy to help!