How does Angular's New Control Flow work?

Photo by Byron Sterk on Unsplash

How does Angular's New Control Flow work?

The most significant change they have ever made to Angular templates

This is one of the biggest features of Angular 17, the new declarative control flow incorporates NgIf, NgFor, and NgSwitch into the framework, while deferrable views allow templates to load dependencies lazily, triggered by one or more configurable conditions.

In June of 2023, the Angular team proposed two RFC, for control flow and deferrable views to have the feedback community. Once the community discussed the new features, the Angular team made a few changes to the original idea, and now, a few months later here we are.

We no longer use Zone.js with our new syntax. Angular is gradually becoming less dependent on this library, which is a significant step. It's important to note that Zone.js will still be used for the old syntax and other features.

@ if @ else

If we compare the old and the new way of using @if, we reduce our code and we don't need to create the id for the else case.

<div *ngIf="userLogged; else noAuthorized">
    Hi {{ userLogged.name }}
</div>

<ng-template #noAuthorized> 
    You need to log in first
</ng-template>
@if (userLogged) {
    Hi {{ userLogged.name }}
} @else {
    You need to log in first
}

@ for

We reduce our code again, but we have to use track in our @for, which is not a bad thing as it improves the performance of our application and we don't have to create the trackByFn function as before.

<div *ngFor="let user of users">
  <option value="{{user.id}}">{{user.name}}</option>
</div>
@for( user of users; track user.id){
    <option value="{{user.id}}">{{user.name}}</option>
}

@ switch

We are reducing our code and making it more readable.

<section [ngSwitch]="user.role">
    <span *ngSwitchCase="'admin'">
     You have admin role
    </span>
    <span *ngSwitchCase="'user'">
     You have user role
    </span>
    <span *ngSwitchCase="'super'">
     You have super role
    </span>
    <span *ngSwitchDefault>
      You don't have a role yet
    </span>
</section>
@switch (user.role) {
    @case (admin) {
        <span>You have admin role</span>
      }
    @case (user) {
        <span>You have user role</span>
      }
    @case (super) {
        <span>You have super role</span>
      }
    @default {
    <span>You don't have a role yet</span>
    }
}

Deferrable Views

This term refers to the fact that views can be rendered when a logical expression is triggered, which we can define as on hover, on interaction, on idle, on timer or on viewport.

There are a few states that we can use to control our view:

  • @ defer: Create a defer block.

  • @ placeholder: (Optional) Initially rendered content before loading.

  • @ error: (Optional) Shows when loading fails.

  • @ loading: (Optional) Shows content while loading.

  • on idle: Triggers on browser's idle time(idle - no user browser interaction).

  • on interaction: Triggers when user interacts with a particular element(on click, focus, touch, input events).

  • on immediate: Immediately loads/ pre-fetches the content.

  • on hover: Triggers on hover event.

  • on timer: Delays loading content for a specified time.

  • on viewport: Loads content when the element gets on the viewport.

  • prefetch: Prefetches the content when a contention is met (using "when" or on "keywords").

  • minium: Shows @loading or @placeholder blocks for minimum X ms.

  • after: Shows @loading or @placeholder blocks after Y ms.

Take a look at a few examples, we have a boolean signal that triggers our content.

isCheckedDefer = signal(false);

We can also add a timer to delay the loading of the content

<input #checkboxDefer type="checkbox" [checked]="isCheckedDefer()" (change)="isCheckedDefer.set(checkboxDefer.checked)" id="checkboxDefer"/>
<label for="checkboxDefer">This checkbox load the <strong>app-about</strong> component</label>
@defer (when isCheckedDefer()) {
  <app-about/>
}
@placeholder {
  <span>Placeholder</span>
}
@error {
  <span>Error</span>
}
@loading(minimum 1s) {
  <span>Loading...</span>
}

In this example we can have 2 components, app-root and app-about, the app-about component is imported in app-root but not downloaded after we need it, when we click the checkbox the component downloads lazy and the placeholder view changes to the loading view, and then when the about component is ready it's finally rendered, if any error happens during the process the @error block will display.

Using @ defer with hover, idle, timer and viewport

As we know, deferred blocks support this declarative trigger type: on interaction, on hover, on idle, on timer, on viewport.

@ defer on interaction

When the user interacts with the @placeholder block, the on interaction, block is rendered. An interaction can refer to a click, touch focus, or input events, etc.

@defer (on interaction) {
  <span>Clicked</span>
}
@placeholder {
  <span>Placeholder (click on it!)</span>
}

@ defer on hover

When the user hovers over the @placeholder block, Angular renders the on-hover block.

@defer (on hover) {
  <span>Hovered</span>
}
@placeholder {
  <span>Placeholder (hover it!)</span>
}

@ defer on idle

Angular shows the on idle section when the browser goes idle after loading the page.

@defer (on idle) {
  <span>Browser has reached an idle state</span>
}
@placeholder {
  <span>Placeholder</span>
}

@ defer on timer

The block for the timer is displayed when the specified duration has elapsed.

@defer (on timer(3s)) {
  <span>Visible after 3s</span>
}
@placeholder {
  <span>Placeholder</span>
}

@ defer on viewport

When the placeholder comes into view in the browser, Angular displays the on viewport block. This is a good way of deferring components that you are not sure will need until they scroll down the page.

@defer (on viewport) {
  <span>The block entered the viewport<span/>
}
@placeholder {
  <span>Placeholder</span>
}

We can open our browser's developer tools and see that the components in @defer are only downloaded when triggered, not only the components but also the directives and pipes are loaded lazily.

Conclusion

In this article, we took a look at the first impressions of Angular's new control flow. These are just a few demos, but I know that this feature has great potential for optimizing our applications, now we need some time to integrate it and see how much it can improve our applications.

Related articles:

Did you find this article valuable?

Support Rubén Peregrina by becoming a sponsor. Any amount is appreciated!