With the release of Angular 17, a much better developer experience is available with the new control flow syntax, which includes the @for syntax.

Previously, we were required to import the ngFor directive from
@angular/common to iterate over arrays in Angular templates.

But now, the @for built-in template syntax simplifies template iteration, offering a looping mechanism that is much more intuitive to developers.

The @for syntax is a better alternative to @if, offering a more concise and performance-safe experience compared to ngFor, without requiring any additional imports.

In this guide, we will dive into all the features available with the @for syntax, and compare it to the conventional ngFor.

Let's dive right in!

Table of Contents

  • Introducing the new @for syntax
  • Breaking down @for step by step
  • What is the @for tracking function?
  • What if there is nothing unique about the looped element?
  • Why is the tracking function now mandatory in @for?
  • How to solve the "NG5002: @for loop must have a "track" expression" error
  • @for with @empty
  • @for with String
  • @for with Iterable objects
  • @for with $index
  • @for with $first and $last
  • @for with $odd and $even
  • @for with $count
  • @for vs. ngFor: A side-by-side comparison
  • Migrating to @for using the Angular CLI
  • Summary

Note: If you are looking for the traditional *ngFor syntax, you can check out my other tutorial here: Angular ngFor: Complete Guide

Looking for @if instead? Check out Angular @if: Complete Guide.

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

Introducing the new @for template syntax

The @for template syntax is to Angular what the traditional for...loop is to JavaScript.

This syntax is built into the Angular template engine itself, so there is no need to import it manually into standalone components, unlike ngFor.

Breaking down @for step by step

Let's take a look at the @for syntax:

@for (item of items; track item) {
  // template content to repeat
}

The @for block is composed of several parts:

  • @for keyword: The @for keyword is the entry point to the @for block. It is followed by a pair of parentheses containing the iteration logic.

  • item: The item statement declares a variable that will be used to represent each item in the collection. This variable is scoped to the
    @for block, and it's not visible outside of it.

  • of items: The of items statement defines the collection to be iterated over. This collection can be an array, a string, or anything that is iterable in general. It doesn't necessarily need to be an array, but that is the most common case.

  • track item: The track item statement is used to track the items by reference. This statement is required. It is used to optimize performance by preventing unnecessary change detection runs when the data changes. If this statement is not included, you will get a compilation error (more on this later).

  • // content to repeat: This is the content to be repeated for each item in the array.

Let's take a look at a simple example of @for in action:

@Component({
  template: `<ul>
    @for (color of colors; track color) {
    <li>{{ color }}</li>
    }
  </ul>`,
})
class ExampleComponent {
  colors = ["Red", "Blue", "White"];
}

Let's break down this example:

  • The @for block is used to iterate over the colors array.

  • The color statement declares a variable named color that will be used to represent each item in the colors array.

  • The of colors statement defines the colors array as the collection to be iterated over.

  • The track color statement is used to track the items by reference because each item is a string. We will see an example with objects later.

In the DOM, the @for block is rendered as follows:

<ul>
  <li>Red</li>
  <li>Blue</li>
  <li>White</li>
</ul>

As you can see, the @for example is very similar to the JavaScript's for..of loop, almost identical:

const colors = ["Red", "Blue", "White"];

for (let color of colors) {
  console.log(color);
}

This is intentional, as the @for syntax is designed to be very familiar and intuitive to JavaScript developers.

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

The most noticeable change when compared to the previous ngFor structural directive is that now the tracking function is mandatory, unlike before.

What is the @for tracking function?

The tracking function created via the track statement is used to make it easy for the Angular change detection mechanism to know exactly what items to update in the DOM after the input array changes.

The tracking function tells Angular how to uniquely identify an element of the list.

For example, if just one element was added to a list of 100, we want Angular to be able to just append that new element to the DOM, without having to re-render all the other elements unnecessarily.

That is just one example of the kind of optimizations that the tracking function enables.

To be effective, the tracking function should identify something unique about the element in the list.

If there isn't anything unique about the element, then the tracking function should return the index of the element in the array (I will show you how to do that later).

Let's see an example of a simple tracking function in a very common scenario - when looping through an array of objects:

@Component({
  template: `<ul>
    @for (course of courses; track course.id) {
    <li>{{ course }}</li>
    }
  </ul>`,
})
class CoursesComponent {
  courses = [
    { id: 1, name: "Angular For Beginners" },
    { id: 2, name: "Angular Core Deep Dive" },
    { id: 3, name: "Angular Forms In Depth" },
  ];
}

As you can see, we are creating a tracking function that uses the id property, which is unique for each course.

Notice also that we are not passing a function in this example to track.

We are instead passing track course.id, which is a shorthand for a function that takes in a course and returns an id.

Angular will take this simplified notation and convert it to a function.

But there are scenarios where the tracking function is not that simple, and we need to write that function ourselves.

Here is an equivalent example to the track course.id shorthand above, but using an actual function:

@Component({
  template: `<ul>
    @for (course of courses; track trackCourse) {
    <li>{{ course }}</li>
    }
  </ul>`,
})
class CoursesComponent {
  courses = [
    { id: 1, name: "Angular For Beginners" },
    { id: 2, name: "Angular Core Deep Dive" },
    { id: 3, name: "Angular Forms In Depth" },
  ];

  trackCourse(index: number, course: Course) {
    return course.id;
  }
}

As you can see, we do still keep the full flexibility of writing our own tracking function, if we really need to.

But in most cases, the shorthand notation will be enough.

What if there is nothing unique about the looped element?

In principle, there should always be something unique about the elements being looped.

For example, in the case of an array of strings you can use the string reference itself because that is guaranteed to be unique:

@Component({
  template: `<ul>
    @for (course of courses; track course) {
    <li>{{ course }}</li>
    }
  </ul>`,
})
class CoursesComponent {
  courses = [
    "Angular For Beginners",
    "Angular Core Deep Dive",
    "Angular Forms In Depth",
  ];
}

In the worst-case scenario, if there is nothing unique about the array elements, you can always safely fall back to the $index of the element, meaning the position of the element in the array.

Using $index is not ideal in terms of the optimizations that could be done, but it already helps in certain scenarios.

Later in the $index section, I will show you how to use it to write a tracking function.

Why is the tracking function now mandatory in @for?

The tracking function is essential to prevent developers from shooting themselves in the foot and experiencing unexpected performance slowdowns in their applications.

The tracking function acts like a safety net.

According to Minko Gechev in the post where @for was introduced:

We often see performance problems in apps due to the lack of trackBy function in *ngFor. A few differences in @forare that track is mandatory to ensure fast diffing performance. In addition, it’s way easier to use since it’s just an expression rather than a method in the component’s class.

As the tracking function is now mandatory, the @for syntax is performance-wise much safer to use than the previous ngFor.

How to solve the "NG5002: @for loop must have a "track" expression" error

For reference, this is the error that the compiler will throw at you if you forget to add a tracking function to your @for block.

Simply add a tracking function using the best practices described above, and the error will be fixed.

@for with @empty

Now in the following sections, we are going to explore all the nice extra features of the @for syntax.

Let's start with the @empty keyword.

The @empty keyword is used to render content when the collection is empty. This is useful for example for displaying a message to the user when an array is empty.

Here is what the @empty syntax looks like:


Example:

```ts
@Component({
  template: `<ul>
    @for (item of items; track item) {
    <li>{{ item }}</li>
    }
    @empty {
    <li>No items found</li>
    }
  </ul>`,
})
class ExampleComponent {
  items = [];
}

Here, the @for block is used to iterate over the items array. The @empty keyword is used to render the message "No items found" only when the items array is empty.

In the DOM, the @for block is rendered as follows:

<ul>
  <li>No items found</li>
</ul>

This is another advantage that @for has over the traditional ngFor directive. The ngFor directive does not have built-in support for rendering content when the collection is empty.

To achieve the same result with ngFor, we would have to use the ngIf directive to check if the collection is empty, and then render the message in that case.

Example:

@Component({
  template: `
    <ul>
      <ng-container *ngFor="let item of items">
        <li>{{ item }}</li>
      </ng-container>
      <ng-container *ngIf="items.length === 0">
        <li>No items found</li>
      </ng-container>
    </ul>
  `,
  standalone: true,
  imports:[..., NgForOf, NgIf]
})
class ExampleComponent {
  items = [];
}

As you can see, it took much less code to implement this in @for. It's much more readable and maintainable.

@for with String

The @for built-in can be used to iterate over strings, as strings are iterable.

This is useful for rendering a string character by character:

Example:

@Component({
  template: `<ul>
    @for (char of "Angular"; track char) {
    <li>{{ char }}</li>
    }
  </ul>`,
})
class ExampleComponent {}

In the DOM, the @for block is rendered as follows:

<ul>
  <li>A</li>
  <li>n</li>
  <li>g</li>
  <li>u</li>
  <li>l</li>
  <li>a</li>
  <li>r</li>
</ul>

@for with Iterable objects

It's worth mentioning that the @for syntax can be used to iterate over any object, not just an array.

Iterable objects are objects that implement the iteration protocol.

Here is an example of how to iterate over an Iterable object, like a Map with two entries:

@Component({
  template: `<ul>
    @for (entry of myMap; track entry) {
    <li>{{ entry[0] }}: {{ entry[1] }}</li>
    }
  </ul>`,
})
class ExampleComponent {
  //This map has two entries. Each entry has a key and a value.
  myMap = new Map([
    ["firstName", "Angular"],
    ["lastName", "Framework"],
  ]);
}

As you can see, the variable myMap is a Map and not an array. Its values can be accessed like this:

myMap.get("firstName");
// 'Angular'
myMap.get("lastName");
//'Framework'

But even though myMap is not an array, we can still loop through it with @for and access its key/value pairs (the Map entries):

<ul>
  <li>firstName: Angular</li>
  <li>lastName: Framework</li>
</ul>

So as you can see, any data type that is Iterable for..of can be used with @for, not just arrays.

@for with $index

The $index implicit variable is supported in the @for control-flow.

$index holds the index of the current row in the array during its iteration by @for.

Note: $index is zero-based, so it starts with 0, 1, 2, etc.

Example:

@Component({
  template: `<ul>
    @for (item of items; track item; let index = $index) {
    <li>{{ index }}: {{ item }}</li>
    }
  </ul>`,
})
class ExampleComponent {
  items = ["Angular", "React", "Vue"];
}

Here, the output will be:

<ul>
  <li>0: Angular</li>
  <li>1: React</li>
  <li>2: Vue</li>
</ul>

@for with $first and $last

The $first and $last implicit variables are also supported in the @for control flow:

  • $first holds a boolean value indicating if the current item is the first item in the collection.
  • $last holds a boolean value indicating if the current item is the last in the collection.

Example:

@Component({
  template: `
    <ul>
      @for (item of items; track item; let first = $first, last = $last) {
      <li>{{ item }}: {{ first }}: {{ last }}</li>
      }
    </ul>
  `,
})
class ExampleComponent {
  items = ["Angular", "React", "Vue"];
}

The output will be:

<ul>
  <li>Angular: true: false</li>
  <li>React: false: false</li>
  <li>Vue: false: true</li>
</ul>

@for with $odd and $even

The $odd and $even implicit variables are also supported:

  • $odd holds a boolean value indicating if the current index is an odd number: 1, 3, 5, 7, 9, etc.
  • $even holds a boolean value indicating if the current index is an even number: 0, 2, 4, 6, 8, etc.

This information is useful, for example for conveniently applying CSS classes to odd and even rows in a data table.

Example:

@Component({
  template: `
    <ul>
      @for (item of items; track item; let odd = $odd, even = $even) {
      <li>{{ item }}: {{ odd }}: {{ even }}</li>
      }
    </ul>
  `,
})
class ExampleComponent {
  items = ["Angular", "React", "Vue"];
}

The output will be:

<ul>
  <li>Angular: false: true</li>
  <li>React: true: false</li>
  <li>Vue: false: true</li>
</ul>

@for with $count

The $count implicit variable is supported in the @for control flow.

It contains the total number of elements of the Iterable that we are looping over:

@Component({
  template: `
    <ul>
      @for (item of items; track item; let count = $count) {
      <li>{{ item }}: {{ count }}</li>
      }
    </ul>
  `,
})
class ExampleComponent {
  items = ["Red", "Blue", "White"];
}

The output will be:

<ul>
  <li>Red: 3</li>
  <li>Blue: 3</li>
  <li>White: 3</li>
</ul>

As you can see, it always prints out 3, which is the length of the array.

@for vs. ngFor: a side-by-side comparison

The traditional *ngFor or NgForOf directive has been used for template iteration in Angular since its creation.

However, the @for syntax offers several advantages over ngFor:

  • Less verbosity: The @for syntax is more concise.

  • The @for now forces developers to use a tracking function

  • The @for syntax is automatically included in templates, no explicit imports are needed.

  • The @for built-in offers improved type inference, enabling TypeScript to more accurately determine types within the iteration block.

Let's take a look at a side-by-side comparison of @for and ngFor:

// @for
@Component({
  template: `<ul>
    @for (item of items; track item) {
    <li>{{ item }}</li>
    }
  </ul>`,
})
class ExampleComponent {
  items = ["Angular", "React", "Vue"];
}

// ngFor
@Component({
  template: `
    <ul>
      <ng-container *ngFor="let item of items">
        <li>{{ item }}</li>
      </ng-container>
    </ul>
  `,
  standalone: true,
  imports:[..., NgForOf]
})
class ExampleComponent {
  items = ["Angular", "React", "Vue"];
}

Migrating to @for using the Angular CLI

Angular made it very easy to migrate to @for from ngFor, thanks to Angular CLI's migration tool.

Just run this command and your project will be migrated to @for:

ng generate @angular/core:control-flow

I hope you enjoyed this post, 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 @for and others, have a look at the Angular Core Deep Dive Course:

Summary

In this guide, we explored in detail the new @for syntax.

We saw how it can be used to iterate over arrays, strings, and any iterable object.

We also saw how it can be used to render content when an array is empty using @empty.

We also learned how we can use it with the $index, $first, $last, $odd, $even, and $count implicit variables that we are familiar with in the traditional NgFor directive.

The @for syntax is more concise, more readable, and safer to use than ngFor, due to the now mandatory tracking function.

Besides, @for does not require manual imports!

So, what do you think about the new @for syntax? Do you like it? Let me know in the comments below.

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