DEV Community

Cover image for New Angular 17 feature: new control flow syntax
Gergely Szerovay for This is Angular

Posted on • Originally published at angularaddicts.com

New Angular 17 feature: new control flow syntax

Angular 17 is going to be released in the beginning of November, and it is going to offer a new template control block syntax with a declarative control flow. Two groups of features use these new blocks:

  • the deferred loading blocks: Angular 17 is going to have a @defer control block enabling lazy-loading of the block's content. Lazy-loading also applies to the dependencies of the content of the block: all the components, directives and pipes are going to be lazy-loaded, too. I demonstrated by examples how defer blocks work in my previous article
  • blocks for providing conditional rendering and rendering the items of a collection (RFC): these are alternatives for the NgIf, NgFor, and NgSwitch directives

One of the most important benefits of these new control blocks is that they will support zoneless applications with Signals.

In this article, I demonstrate how to:

  • use the new control block syntax and create conditionally rendered blocks with @if and @else
  • create switch and case blocks with @switch, @case and @default
  • create loops with @for, and handle empty collections with the @empty block
  • migrate ngIf, ngFor, and ngSwitch to the new control block syntax

The full source code is available here:

https://github.com/gergelyszerovay/angular-17-control-flow

I used Angular v17.0.0-next.8 with standalone components and Signals. You can start the frontend by yarn run start or npm run start.

Conditionally rendered control blocks: @if and @else

In the first example, I create a checkbox and bind it to the isChecked signal. The signal's default value is true, so initially the checkbox is checked, and the content of the @if block is rendered. The examples below are from the src\app\app.component.html template file:

<h3>&#64;if and &#64;else</h3>
<div>
  <input #checkbox type="checkbox" [checked]="isChecked()" (change)="isChecked.set(checkbox.checked)" id="checkbox"/>
</div>
<div>
@if (isChecked()) {
  <span>Checked</span>
} 
@else {
  <span>Not checked</span>
}
</div>
Enter fullscreen mode Exit fullscreen mode

The @if (logical_expression) { statement creates a @if block with a logical expression. I use the isChecked() signal as a logical expression, as it evaluates to a boolean value.

I added an @else block under the @if block, it's rendered when the logical expression in the @if block evaluates to false, so in our case if the value of the isChecked() signal is false. So if I uncheck the checkbox, Angular renders the contents of the @else block.

There is one more important thing regarding the new control block syntax: since the @, { and }characters have a special meaning, we have to replace them in the text of the templates using their HTML entities. We need to use:

  • &#64; instead of @, see the &lt;h3> heading in my code above
  • &#123; instead of {
  • &#125; instead of }

Otherwise we get one of the following compile-time errors:

  • [ERROR] NG5002: Incomplete block "...". If you meant to write the @ character, you should use the "@" HTML entity instead. [plugin angular-compiler]
  • [ERROR] NG5002: Unexpected character "EOF" (Do you have an unescaped "{" in your template? Use "{{ '{' }}") to escape it.)

Using @for blocks to render items of a collection

Let's define the items array in the component's class:

  collection = [
    { id: 1, name: 'Item 1' },
    { id: 2, name: 'Item 2' },
    { id: 3, name: 'Item 3' }
  ];
Enter fullscreen mode Exit fullscreen mode

We can use an @for (item of items; track item.id) {block to render a collection's elements:

<ul>
@for (item of collection; track item.id; let index = $index, first = $first; let last = $last, even = $even, odd = $odd; let count = $count) {
<li><strong>{{item.name}}</strong> index={{index}} first={{first}} last={{last}} even={{even}} odd={{odd}} count={{count}}</li>
}
</ul>
Enter fullscreen mode Exit fullscreen mode

Each item in the collection needs to have a unique property (like the id), and we need to refer to this value with the track argument. If the collection contains strings or numbers, not objects, we can use the item itself as a track value: @for (item of items; track item) {.

Next to the current item, the @for expression enables us to access the following values inside the block:

  • $index: the index of the item in the collection
  • $even: true, if the index is even
  • $odd: true, if the index is odd
  • $count: the number of the items in the collection
  • $first: true, if the current item is the first in the collection
  • $last: true, if the current item is the last in the collection

Using an @empty block to handle the empty collections passed to @for

We can add an @empty block under the @for block. The @empty block's content is rendered if the collection we passed to the @for block is empty:

<ul>
@for (item of emptyCollection; track item.id;) {
<li><strong>{{item.name}}</strong></li>
}
@empty {
  <span>The collection is empty</span>
}
</ul>
Enter fullscreen mode Exit fullscreen mode

Switch control flow with with @switch, @case and @default

In my next example, I create four radio buttons and a radioValue signal. The signal's initial value is 1, and when the user clicks on the radio buttons, we change the signaal's value to 1, 2, 3 or 4:

<div>
  <div>
    <input type="radio" [checked]="radioValue() === 1" (change)="radioValue.set(1)" id="radio1"/>
    <label for="radio1">1</label>
  </div>
  <div>
    <input type="radio" [checked]="radioValue() === 2" (change)="radioValue.set(2)" id="radio2"/>
    <label for="radio2">2</label>
  </div>
  <div>
    <input type="radio" [checked]="radioValue() === 3" (change)="radioValue.set(3)" id="radio3"/>
    <label for="radio3">3</label>
  </div>
  <div>
    <input type="radio" [checked]="radioValue() === 4" (change)="radioValue.set(4)" id="radio4"/>
    <label for="radio4">4</label>
  </div>
</div>
<div>
@switch (radioValue()) {
  @case (1) {
    <span>Case 1</span>
  }
  @case (2) {
    <span>Case 2</span>
  }
  @default {
    <span>Default case (Not 1 or 2)</span>
  }
}
</div>
Enter fullscreen mode Exit fullscreen mode

I nested three blocks into the @switch block:

  • the contents of the @case (1) { block is rendered, when the radioValue() signal equals to 1
  • the contents of the @case (2) { block is rendered, when the radioValue() signal equals to 2
  • and the contents of the @default { block is rendered, when the radioValue() signal doesn't equal to the values we specified in the @case blocks

How to migrate ngIf, ngFor, and ngSwitch to the new control block syntax

To convert the old structural directives in an app's templates to the new control blocks, run the following schematic:

ng g @angular/core:control-flow-migration
Enter fullscreen mode Exit fullscreen mode

Summary

In this article, I demonstrated how the new control flow in Angular 17 is going to work: I showed you how to create conditional blocks and loops using the new control block syntax. I hope you have found my tutorial useful!

In the first part of this article series, I explain how the new deferred blocks work and how to specify conditions to trigger the loading and rendering of these blocks' content: New Angular 17 feature: deferred loading.

And as always, please let me know if you have some feedback!

πŸ‘¨β€πŸ’»About the author

My name is Gergely Szerovay, I work as a frontend development chapter lead. Teaching (and learning) Angular is one of my passions. I consume content related to Angular on a daily basis β€” articles, podcasts, conference talks, you name it.

I created the Angular Addict Newsletter so that I can send you the best resources I come across each month. Whether you are a seasoned Angular Addict or a beginner, I got you covered.

Next to the newsletter, I also have a publication called β€” you guessed it β€” Angular Addicts. It is a collection of the resources I find most informative and interesting. Let me know if you would like to be included as a writer.

Let’s learn Angular together! Subscribe here πŸ”₯

Follow me on Substack, Medium, Dev.to, Twitter or LinkedIn to learn more about Angular!

Top comments (1)

Collapse
 
rahilka profile image
Rahilka Simonova

Gergely, thanks for this in-depth guide! You have some great examples here that fits perfect into the idea behind the new control flow.
I really love this new syntax, I think it's much clearer to use. I wrote a blog bost to share my thoughts as well, feel free to check it out : https://www.linkedin.com/posts/activity-7132687950542176257-bCNQ?utm_source=share&utm_medium=member_android