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
Related Articles
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
: Theitem
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
: Theof 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
: Thetrack 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 thecolors
array. -
The
color
statement declares a variable namedcolor
that will be used to represent each item in thecolors
array. -
The
of colors
statement defines thecolors
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@for
are thattrack
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!