Michi DeWitt | ng-conf | Oct 2020
This is part of an ongoing series covering all the built-in Angular structural directives. This series is intended for new and experienced Angular developers. Previously we covered the ngIf directive.
Introduction
In Part 2 of our Angular Structural Directive series we’ll be covering the ngFor directive. Like the ngIf directive we covered previously, this directive is added as an attribute to any element in an HTML template.
What is the NgFor Directive and How is it Used?
The ngFor directive is used to iterate over a collection of objects and add an instance of a specified template for each item in the collection.
Okay — that sounds very technical. What does that mean?
Here’s a simple example.
I’ve created a custom To Do list application for myself (because all demos must use To Do lists, right?). In my application, I have the following component that shows all my To Do list items:
<my-todo-list>
<my-todo-item
title="Walk Dog"
description="Take the dog for a walk befor the gym">
</my-todo-item>
<my-todo-item
title="Gym"
description="Gym class at 5:30pm">
</my-todo-item>
<my-todo-item
title="Blog Post"
description="Write NgFor blog post">
</my-todo-item>
</my-todo-list>
Here I have 3 items on my To Do list — “Walk Dog”, “Gym”, and “Blog Post”.
There are two problems with the list in this format.
First, this is a very static. If I want to add or remove something from my To Do list, I need to change the code and re-deploy my application.
Second and potentially more problematic, I can’t have any other people use my application because they would have no way to edit their own To Do list. This isn’t very flexible.
Instead, I can use the ngFor directive to iterate over a list of To Do items and add a new my-todo-item component for each item in my list.
Here’s the new code:
@Component({
selector: 'my-ng-for-example',
templateUrl: './my-ng-for-example.component.html',
})
export class MyNgForExample {
public todoItems: TodoItem[] = [
{
title: "'Walk Dog',"
description: 'Take the dog for a walk before the Gym'
},
{
title: "'Gym',"
description: 'Gym class at 5:30pm'
},
{
title: "'Blog Post',"
description: 'Write ngFor Blog Post'
}
]
}
export interface TodoItem {
title: "string;"
description: "string;"
}
<my-todo-list>
<my-todo-item *ngFor="let item of todoItems"
[title]="item.title"
[description]="item.description">
</my-todo-item>
</my-todo-list>
Note: There is no example of the code in the MyTodoItemComponent, but you can assume it shows all the data a user needs to see in the To Do card and provides a UI for any actions on the card.
Much better! The refactored code that utilizes the ngFor directive has the following improvements:
- DRY — I am not repeating the same block of code for each To Do item.
- Dynamic — I can hook the todoItems array up to an Observable returned by a service or store so the list is always up to date and can be changed without re-deploying my code.
- Extendible — If I want to add additional features, such as a button or completed state, I just have to update the one line in my template and all my To Do list items in the template will be updated.
Basic Usage
Let’s take a step back and see how the ngFor directive works. The directive was added using the following snippet of code
*ngFor="let item of todoItems"
Let’s break that down.
- The directive is added using the
*syntax and the name of the directive —ngFor. The*let’s any developer know the attribute represents a structural directive -
todoItemsis a public property in my component that represents an array of data, in this case an array ofTodoItemobjects - The code
<my-todo-item *ngFor="let item of todoItems" ...></my-todo-item>tells the compiler to add a newmy-todo-itemcomponent for eachitemin thetodoItemsarray. The directive will do this by iterating over each object in thetodoItemsarray and keeps a reference to each object in a variable calleditem - Finally, I am able to customize each
MyTodoItemComponentby referencing theitemvariable and binding data from theitemto theMyTodoItemComponentinputs with<my-todo-item *ngFor="let item of todoItems" [title]="item.title" [description]="item.description"></my-todo-item>
Like the ngIf directive, the ngFor directive is easy to use because it maps so closely to the for-of Javascript syntax many Angular developers are already familiar with. You can mentally map the HTML syntax above to:
for (let item of todoItems) {
// Execute this code for each item in todoItems
console.log(`${item.title}: ${item.description}`);
}
Using Exported NgFor variables
Sometimes you need to know additional information about each object’s position in the array to properly render each instance of the template.
Some common needs are:
- Add a different CSS class for every other element (example: different colors for rows in a table)
- Know the index of the object in the array when an action is executed (there will be an example for this later on)
- Know if the item is the first or last item in the array (example: add a margin to the bottom of the template if the item is not the last item in the array)
Luckily, Angular exports variables for all of these use cases!
- Need to add a different CSS class for every other element? Use
evenorodd - Need to know the of the index the object in the array when an action is executed? Use
index - Need to know if the item is the first or last item in the array? Use
firstorlast, respectively
Angular has documentation for all the variables that are available, but we’ll use index as an example in this post.
Let’s go back to our To Do list example. I want to add a new feature to delete an item from my To Do list (I don’t feel like going to the gym today). Let’s say the MyTodoItem component has a delete button that will emit an event whenever a user clicks on the button. But it’s the responsibility of the owner of the list to actually remove the To Do item from the list.
I’ve added a simple delete function, deleteItem, that will remove the To Do item at a specified index from my array of To Do items.
@Component({
selector: 'my-ng-for-example',
templateUrl: './my-ng-for-example.component.html',
})
export class MyNgForExample {
public todoItems: TodoItem[] = [...];
public deleteItem(index: number): void {
todoItems.splice(index, 1);
}
}
Now I’ll update the template to listen for delete output events from the MyTodoItemComponent.
<my-todo-list>
<my-todo-item *ngFor="let item of todoItems; index as i"
[title]="item.title"
[description]="item.description"
(delete)="deleteItem(i)">
</my-todo-item>
</my-todo-list>
That’s all I need! Now each time the delete event is emitted for any To Do list item, the deleteItem function will be called with the index of item the delete output event fired from.
Let’s break down what code we added:
- We stored the
indexvariable that Angular provides using theassyntax in thengFordirective with the codeindex as i. Now we can reference the index for each item with theivariable. - We added a listener on the
deleteoutput with(delete)="deleteItem(i)". This calls thedeleteItemfunction we added earlier and passes in theivariable we created fromngForindexlocal variable.
This example used the index variable specifically, but the same syntax can be used to store any of the other local variables, and there is no limit for how many variables may be added. That means you could do something like:
<my-todo-item *ngFor="let item of todoItems; index as i; even as
isEven; first as isFirst">
...
</my-todo-item>
Tracking Changes
The one bit of magic we haven’t covered yet is how changes are tracked. The ngFor directive will track changes in the array and will update the DOM whenever a change is detected for an object in the array. The following changes will trigger an update so the DOM properly reflects the contents of the array:
- A new object is added to the array
- A object is removed from the array (this is why the delete function we added above works)
- The items in the array are re-ordered
No additional code is required from the developer to make change detection work.
By default, the changes are tracked using the reference identity of each object in the array. The downside of tracking using the reference identity is that if you’re using a Reactive Programming strategy with immutable arrays and objects, you may be rebuilding all the objects in the array. This will cause the DOM to flash when there is a change to the array because it’s rebuilding each element created by the ngFor directive. Even if only one element was added or removed. Not only is this ugly, this can be very expensive!
As with other problems we’ve encountered, Angular provides a simple solution for this: add a custom trackBy function that is used to track changes. The trackBy function allows the developer to specify which value should be used to compare the objects if the object has changed. Generally, it’s best to return a unique id for the object that does not change when the object is rebuilt.
Let’s take a look at track by in action.
@Component({
selector: 'my-ng-for-example',
templateUrl: './my-ng-for-example.component.html',
})
export class MyNgForExample {
public todoItems: TodoItem[] = [
{
id: '1',
title: "'Walk Dog',"
description: 'Take the dog for a walk before the Gym'
},
{
id: '2',
title: "'Gym',"
description: 'Gym class at 5:30pm'
},
{
id: '3',
title: "'Blog Post',"
description: 'Write ngFor Blog Post'
}
];
public deleteItem(index: number): void { ... }
public trackByFn(index: number, item: TodoItem): string {
return item.id;
}
}
export interface TodoItem {
id: string;
title: "string;"
description: "string;"
}
In the code above, I’ve made the following changes:
- Updated the
TodoIteminterface to added a unique id for each To Do item - Updated our array to add an id to each To Do item in the array
- Added a track by function that returns the id of the To Do item
Next, we’ll update the template:
<my-todo-list>
<my-todo-item *ngFor="let item of todoItems; index as i; trackBy: trackByFn"
[title]="item.title"
[description]="item.description"
(delete)="deleteItem(i)">
</my-todo-item>
</my-todo-list>
Now, no matter how we update the array of todoItems, we’ll only rebuild the parts of the DOM that change rather than rebuilding the entire template.
Let’s take a step back and break down these changes we made.
- We added the track by function to the
ngFordirective with the following codetrackBy: trackByFn. This tells thengFordirective to use the function namedtrackByFnin my component to retrieve the value used to compare the changes to the array rather than the reference identity of the object. - We implemented the track by function in the component which takes in the
indexof the object in the array and the object itself. We return the value held in the id of the object. (In our case, we didn’t need to use the index). - Now when we add or remove an element to the
todoItemsarray, instead of checking the reference identity of each item in thetodoItemsarray, the directive will compare the id of the object before the change and the id after. If they are the same, it will rebuild the DOM item for that particular To Do item.
That’s all we need to do to use our own custom logic to track changes in the ngFor directive!
Summary
We’ve now successfully used the ngFor directive to make a dynamic To Do list component that can be used by any user of our application.
Let’s go over the main points:
🛠 The ngFor directive is allows you to iterate over an array and add a new instance of a template for each item in the array.
ℹ️ Make use of the local variables Angular provides such as index, even, and first to get additional information about the position of each object in the array as you are iterating through.
🎯 Use the trackBy function to have better control over when the DOM rebuilds your template when you make changes to the array bound to your ngFor directive.
Now you are ready to use the ngFor directive in your next Angular application. Happy coding!
Additional Resources
Angular *ngFor Documentation: https://angular.io/api/common/NgForOf
ng-conf: The Musical is coming
ng-conf: The Musical is a two-day conference from the ng-conf folks coming on April 22nd & 23rd, 2021. Check it out at ng-conf.org

Top comments (0)