Introducing the New @for Template Syntax
The @for template syntax serves the same purpose in Angular that the traditional for...loop serves in JavaScript.
This syntax comes built into the Angular template engine, eliminating the need for manual imports in standalone components, which was previously required with ngFor.
Breaking Down @for Step by Step
Let's examine the @for syntax:
@for (item of items; track item) {
// template content to repeat
}
The @for block consists of several distinct elements:
-
@forkeyword: The@forkeyword marks the beginning of the@forblock. Immediately following it is a pair of parentheses that contains the iteration logic. -
item: Theitemdeclaration creates a variable representing each element in the collection. This variable exists only within the scope of the@forblock and remains inaccessible outside of it. -
of items: Theof itemsportion specifies which collection will be iterated. This collection could be an array, a string, or any other iterable data structure. While arrays are the most common use case, they're by no means the only option. -
track item: Thetrack itemportion handles item tracking by reference. This is a mandatory component that optimizes performance by reducing unnecessary change detection cycles when data changes. Omitting this results in a compilation error (we'll discuss this shortly). -
// content to repeat: This represents the content that gets repeated for every item in the collection.
Here's a basic demonstration of @for in practice:
@Component({
template: `<ul>
@for (color of colors; track color) {
<li>{{ color }}</li>
}
</ul>`,
})
class ExampleComponent {
colors = ["Red", "Blue", "White"];
}
Let's analyze what's happening here:
-
The
@forblock iterates through thecolorsarray. -
The variable
coloris declared to hold each individual value from thecolorsarray during iteration. -
The
of colorsportion designates thecolorsarray as the iteration source. -
The
track colorstatement tracks items by reference, which works here because each item is a string. We'll explore object tracking with examples later.
In the rendered DOM, the @for block produces:
<ul>
<li>Red</li>
<li>Blue</li>
<li>White</li>
</ul>
As you can observe, the @for example closely mirrors JavaScript's for..of loop, with striking similarity:
const colors = ["Red", "Blue", "White"];
for (let color of colors) {
console.log(color);
}
This resemblance is deliberate—the @for syntax aims to feel natural and instinctive to JavaScript developers.
Here's a video from our YouTube channel showcasing the @for syntax in action:
The most significant difference compared to the earlier ngFor structural directive is that the tracking function has now become a requirement, unlike its predecessor.
What Is the @for Tracking Function?
The tracking function, established through the track statement, assists Angular's change detection mechanism in determining precisely which DOM items need updating when the input array undergoes changes.
This function instructs Angular on how to uniquely distinguish each element within the list.
Consider this scenario: if a single item gets added to a list of 100 elements, we'd want Angular to simply append that new element to the DOM without re-rendering the other 99 items unnecessarily.
That represents just one example of the optimizations made possible by the tracking function.
For optimal effectiveness, the tracking function should reference something distinct about each element within the list.
In situations where no unique property exists, the tracking function should return the element's index position within the array (I'll demonstrate this approach later).
Let's examine a typical tracking function example—iterating 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" },
];
}
Notice here that we're utilizing the id property for tracking, which is unique to each course object.
It's worth noting that in this example, we're not providing a complete function to track.
Instead, we're using track course.id, which serves as shorthand for a function that receives a course and returns its id.
Angular takes this abbreviated notation and translates it into an actual function internally.
However, there are situations where the tracking logic becomes more complex, requiring us to write the function explicitly.
Here's the equivalent implementation using an explicit function instead of the track course.id shorthand:
@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 demonstrated, the full flexibility of writing custom tracking functions remains available when needed.
Yet, in the majority of cases, the shorthand notation proves sufficient.
What If There's Nothing Unique About the Looped Element?
In theory, some unique characteristic should always exist among the elements being looped.
For instance, when working with string arrays, you can use the string reference itself, as it's 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 most challenging scenario, when no unique property exists, you can safely default to using $index—the element's position within the array.
While $index doesn't provide ideal optimization potential, it still offers benefits in certain situations.
In the $index section later, I'll demonstrate how to construct a tracking function using it.
Why Is the Tracking Function Now Mandatory in @for?
The tracking function serves as essential protection, preventing developers from unknowingly degrading their application's performance.
Think of the tracking function as a safeguard mechanism.
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 thattrackis 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.
With the tracking function now being compulsory, the @for syntax offers substantially better performance safety compared to the previous ngFor approach.
How to Solve the "NG5002: @for loop must have a "track" expression" Error
For your reference, this is the error message the compiler produces when you omit a tracking function from your @for block.
To resolve this, simply incorporate a tracking function following the best practices outlined earlier, and the error will disappear.
@for with @empty
In the upcoming sections, we'll explore the various additional features that the @for syntax offers.
Let's begin with the @empty keyword.
The @empty keyword renders content when the collection being iterated is empty. This proves handy, for instance, when you want to display a user message indicating that an array contains no items.
Here's 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 = [];
}
In this example, the @for block iterates over the items array. The @empty keyword renders the message "No items found" exclusively when the items array is empty.
In the rendered DOM, this appears as:
<ul>
<li>No items found</li>
</ul>
This represents another advantage of @for over the traditional ngFor directive. The ngFor directive lacks built-in support for displaying content when the collection is empty.
To replicate this behavior with ngFor, you'd need to combine it with the ngIf directive to check for an empty collection before conditionally rendering the message.
Here's an 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, achieving this with @for requires considerably less code. It's significantly more readable and maintainable.
@for with String
The @for built-in works with strings since strings are iterable data types.
This capability proves useful when you want to render a string character by character:
Here's an example:
@Component({
template: `<ul>
@for (char of "Angular"; track char) {
<li>{{ char }}</li>
}
</ul>`,
})
class ExampleComponent {}
In the rendered DOM, this produces:
<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 important to note that the @for syntax extends beyond arrays—it can iterate over any iterable object.
Iterable objects are those that implement the iteration protocol.
Here's an illustration of iterating over an Iterable object, specifically a Map containing 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 shown, myMap is a Map rather than an array. Its values can be accessed in this manner:
myMap.get("firstName");
// 'Angular'
myMap.get("lastName");
//'Framework'
However, even though myMap isn't an array, we can still iterate through it using @for and access its key/value pairs (the Map entries):
<ul>
<li>firstName: Angular</li>
<li>lastName: Framework</li>
</ul>
This demonstrates that any data type compatible with for..of can be used with @for, not just arrays.
@for with $index
The $index implicit variable is available within the @for control-flow.
$indexstores the current position within the array during iteration by@for.
Important: $index uses zero-based indexing, meaning it starts at 0, then 1, 2, and so on.
Here's an example:
@Component({
template: `<ul>
@for (item of items; track item; let index = $index) {
<li>{{ index }}: {{ item }}</li>
}
</ul>`,
})
class ExampleComponent {
items = ["Angular", "React", "Vue"];
}
The resulting 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 likewise supported within the @for control flow:
$firstcontains a boolean value indicating whether the current item is the initial item in the collection.$lastcontains a boolean value indicating whether the current item is the final item in the collection.
Here's an 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"];
}
This will produce the following output:
<ul>
<li>Angular: true: false</li>
<li>React: false: false</li>
<li>Vue: false: true</li>
</ul>
Leveraging $odd and $even in @for
The @for block also provides the $odd and $even implicit variables:
$oddreturns a boolean that istruewhen the index is odd (1, 3, 5, 7, 9, etc.).$evenreturns a boolean that istruewhen the index is even (0, 2, 4, 6, 8, etc.).
These flags are handy for tasks like assigning alternating CSS classes to rows in a table or any list-based UI.
Consider this 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"];
}
This will render the following output:
<ul>
<li>Angular: false: true</li>
<li>React: true: false</li>
<li>Vue: false: true</li>
</ul>
Understanding $count within @for
The $count implicit variable is accessible inside the @for control flow block.
It represents the total number of items in the collection being iterated 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 resulting output is:
<ul>
<li>Red: 3</li>
<li>Blue: 3</li>
<li>White: 3</li>
</ul>
Notice that it consistently shows 3, reflecting the array's length.
Since Angular's inception, the *ngFor (or NgForOf) directive has been the standard method for iterating in templates.
The newer @for syntax brings multiple benefits compared to its predecessor:
-
Reduced boilerplate: The
@forsyntax is more succinct and direct. -
Mandatory tracking: It enforces the use of a tracking function, which is a key improvement for performance.
-
No imports needed: The syntax is built into Angular's template compiler and requires no explicit module imports.
-
Superior type checking: The built-in offers more precise type inference, leading to better TypeScript type safety within the loop's scope.
Here is a direct side-by-side look at both methods:
// @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"];
}
Simplifying Migration with the Angular CLI
Angular provides a straightforward path to switch from ngFor to @for through its CLI migration schematics.
Execute the following command, and the CLI will automatically update your project:
ng generate @angular/core:control-flow
We hope this guide proves helpful. To stay informed about future posts on similar topics, consider subscribing to our newsletter:
Subscribers also receive the latest updates within the Angular ecosystem.
For an in-depth exploration of Angular core features, including @for and more, check out the Angular Core Deep Dive Course:
Wrap-Up
This guide has taken a detailed look at the modern @for control flow syntax.
We've demonstrated how to use it for iterating over arrays, strings, and any other iterable entity.
We also covered the @empty block, a feature that allows you to show specific content when the collection is empty.
Furthermore, we explained the utility of the implicit variables—$index, $first, $last, $odd, $even, and $count—which you may recognize from the older NgFor directive.
The @for syntax stands out as being cleaner, easier to read, and more reliable than ngFor, primarily because the mandatory tracking function results in better performance and fewer bugs.
Additionally, there is no need for manual imports with @for!
What are your thoughts on the new @for syntax? We'd love to hear your feedback in the comments section.
If you have any questions, please don't hesitate to leave a comment as well. We're here to assist.
