In this post you’re going to learn how to use Angular’s NgFor directive to loop over data to render data or components. Rendering a list of <todo-item>
components would be a great use-case for NgFor.
As Angular is a reactive framework, it’s common to see NgFor being used alongside observables, and so our code examples will also follow a reactive style. NgFor does support arrays and array-like objects as well – we’ll explore each approach.
What is NgFor?
NgFor is one of the most commonly used Angular directives that comes with Angular’s CommonModule.
🙌 Tip: Include the BrowserModule
in the root module of your app, as it already includes the CommonModule
for us!
NgFor allows us to loop over data and access each value
and index
– much like a regular Array ForEach.
The NgFor directive also does far more than just loop and give us a value and index, it can be combined with observables via the async
pipe or enhance our rendering performance with the trackBy
function we can provide.
For this article, we’ll be including a further ContactCardComponent
component in our @NgModule
:
// ...
import { ContactCardComponent } from './contact-card.component';
@NgModule({
declarations: [AppComponent, ContactCardComponent],
// ...
})
export class AppModule {}
Our ContactCardComponent
takes a single @Input
of contact
:
import { Component, Input } from '@angular/core';
import { Contact } from './models/contact.interface';
@Component({
selector: 'contact-card',
template: `
<div class="contact-card">
<p>{{ contact.name }} ( {{ contact.age }} )</p>
<p>{{ contact.email }}</p>
</div>
`,
})
export class ContactCardComponent {
@Input() contact: Contact;
}
So now we’re all setup, what’s next?
Iterating collections
Now that our ContactCardComponent
is included in our module, we can setup our AppComponent
to use this dataset:
@Component({...})
export class AppComponent implements OnInit {
contacts: Observable<Contact[]>;
ngOnInit() {
this.contacts = Observable.of([
{
"id": 1,
"name": "Laura",
"email": "lbutler0@latimes.com",
"age": 47
},
{
"id": 2,
"name": "Walter",
"email": "wkelley1@goodreads.com",
"age": 37
},
{
"id": 3,
"name": "Walter",
"email": "wgutierrez2@smugmug.com",
"age": 49
},
{
"id": 4,
"name": "Jesse",
"email": "jarnold3@com.com",
"age": 47
},
{
"id": 5,
"name": "Irene",
"email": "iduncan4@oakley.com",
"age": 33
}
]);
}
}
As mentioned in the introduction, I’m using Observable.of
here from RxJS to give me an Observable stream from the results, this is a nice way to mimic an Observable response, such as when using Angular’s HttpClient
module to return data from an API.
ngFor in practice
Now we’re setup, we can look into our AppComponent
template:
@Component({
selector: 'app-root',
template: `
<div class="app">
<ul>
<li>
<contact-card></contact-card>
</li>
</ul>
</div>
`
})
You can see I’m declaring <contact-card>
inside of here, as we want to iterate our dataset and populate each contact via the @Input
setup inside our ContactCardComponent
.
One way we could do this is using ngFor
on the component itself, however for simplicity we’ll use the unordered list. Let’s add ngFor
:
<ul>
<li *ngFor="let contact of contacts">
<contact-card></contact-card>
</li>
</ul>
There are a few things happening here, the first you’ll notice a *
character at the beginning of the ngFor
, we’ll come onto what this means in the next section when we look at the <ng-template>
element. Secondly, we’re creating a context called contact
, using a “for of” loop.
The ngFor
Directive will clone the <li>
and the child nodes. In this case, the <contact-card>
is a child node, and a card will be “stamped out” in the DOM for each particular item inside our contacts
collection.
So, now we have contact
available as an individual Object, we can pass the individual contact
into the “:
<ul>
<li *ngFor="let contact of contacts">
<contact-card [contact]="contact"></contact-card>
</li>
</ul>
If you’re using a static array, or binding the result of an Observable to the template, you can leave the template as it currently is. However, we can optionally bind the Observable directly to the template, which means we’ll need the async
pipe here to finish things off:
<ul>
<li *ngFor="let contact of contacts | async">
<contact-card [contact]="contact"></contact-card>
</li>
</ul>
Using trackBy for keys
If you’re coming from an AngularJS background, you’ll have likely seen “track by” when using an ng-repeat
, and similarly in React land, using key
on a collection item.
So what do these do? They associate the objects, or keys, with the particular DOM nodes, so should anything change or need to be re-rendered, the framework can do this much more efficiently. Angular’s ngFor
defaults to using object identity checking for you, which is fast, but can be faster!
This is where trackBy
comes into play, let’s add some more code then explain:
<ul>
<li *ngFor="let contact of contacts | async; trackBy: trackById;">
<contact-card [contact]="contact"></contact-card>
</li>
</ul>
Here we’ve added trackBy
, then given it a value of trackById
. This is a function that we’ll add in the component class:
trackById(index, contact) {
return contact.id;
}
All this function does is use a custom tracking solution for our collection. Instead of using object identity, we’re telling Angular here to use the unique id
property that each contact
object contains. Optionally, we can use the index
(which is the index in the collection of each item, i.e. 0, 1, 2, 3, 4).
If your API returns unique data, then using that would be a preferable solution over index
– as the index
may be subject to change if you reorder your collection. Using a unique identifier allows Angular to locate that DOM node associated with the object much faster, and it will reuse the component in the DOM should it need to be updated – instead of destroying it and rebuilding it.
Capturing “index” and “count”
The ngFor
directive doesn’t just stop at iteration, it also provides us a few other niceties. Let’s explore index
and count
, two public properties exposed to us on each ngFor
iteration.
Let’s create another variable called i
, which we’ll assign the value of index
to. Angular exposes these values under-the-hood for us, and when we look at the next section with the <ng-template>
element, we can see how they are composed.
To log out the index, we can simply interpolate i
:
<ul>
<li *ngFor="let contact of contacts | async; index as i;">
Index: {{ i }}
<contact-card [contact]="contact"></contact-card>
</li>
</ul>
This will give us every index, starting from 0
, for each item in our collection. Let’s also expose count
:
<ul>
<li *ngFor="let contact of contacts | async; index as i; count as c;">
Index: {{ i }}
Count: {{ c }}
<contact-card [contact]="contact"></contact-card>
</li>
</ul>
The count
will return a live collection length, equivalent of contacts.length
. These can optionally be bound and passed into each component, for example you may wish to log out the total length of your collection somewhere, and also pass the index
of the particular contact into a function @Output
:
<ul>
<li *ngFor="let contact of contacts | async; index as i; count as c;">
<contact-card
[contact]="contact"
[collectionLength]="c"
(update)="onUpdate($event, i)">
</contact-card>
</li>
</ul>
Accessing first, last, odd, even
Four more properties exposed by ngFor
(well, actually underneath it uses NgForOfContext
, a class which generates each ngFor
context internally). Let’s quickly look at the source code for this:
export class NgForOfContext<T, U extends NgIterable<T> = NgIterable<T>> {
constructor(public $implicit: T, public ngForOf: U, public index: number, public count: number) {}
get first(): boolean {
return this.index === 0;
}
get last(): boolean {
return this.index === this.count - 1;
}
get even(): boolean {
return this.index % 2 === 0;
}
get odd(): boolean {
return !this.even;
}
}
As I mentioned above, the NgForOfContext
is what constructs our ngFor
items, and you can see in the constructor
we’ve already taken a look at index
and count
! The last things we need to look at are the getters, which we can explain from the source code above:
- first: returns
true
for the first item in the collection, matches the index with zero - last: returns
true
for the last item in the collection, matches the index with the total count, minus one to shift the “count” down one to cater for zero-based indexes - even: returns
true
for even items (e.g. 2, 4) in the collection, uses%
modulus operator to calculate based off index - odd: returns
true
for odd items (e.g. 1, 3), simply invertsthis.even
result
Using this, we can add conditionally apply things such as styling, or hook into the last
property to know when the collection has finished rendering.
For this quick demo, we’ll use ngClass
to add some styles to each <li>
(note how we create more variables, just like index
):
<ul>
<li
*ngFor="let contact of contacts | async; odd as o; even as e;"
[ngClass]="{
'odd-active': o,
'even-active': e
}">
<contact-card
[contact]="contact"
(update)="onUpdate($event, index)">
</contact-card>
</li>
</ul>
And some styles:
@Component({
selector: 'app-root',
styles: [`
.odd-active { background: purple; color: #fff; }
.even-active { background: red; color: #fff; }
`],
template: `
<div class="app">
<ul>
<li
*ngFor="let contact of contacts | async; odd as o; even as e;"
[ngClass]="{
'odd-active': o,
'even-active': e
}">
<contact-card
[contact]="contact"
(update)="onUpdate($event, index)">
</contact-card>
</li>
</ul>
</div>
`
})
We won’t demonstrate first
and last
, as it’s fairly obvious from the above how we can hook those up!
element
We mentioned earlier in this article that we’d look at understanding what the *
meant in our templates. This also shares the same syntax as *ngIf
, which you’ve likely also seen before.
So in this next section, we’ll take a deeper dive on ngFor
, *
and the <ng-template>
element to explain in more detail what’s really happening here.
When using an asterisk (*
) in our templates, we are informing Angular we’re using a structural directive, which is also sugar syntax (a nice short hand) for using the <ng-template>
element.
and Web Components
So, what is the <ng-template>
element? First, let’s take a step back. We’ll roll back to showing some AngularJS code here, perhaps you’ve done this before or done something similar in another framework/library:
<script id="myTemplate" type="text/ng-template">
<div>
My awesome template!
</div>
</script>
This overrides the type
on the <script>
tag, which prevents the JavaScript engine from parsing the contents of the <script>
tag. This allows us, or a framework such as AngularJS, to fetch the contents of the script tag and use it as some form of HTML template.
Web Components introduced a new spec a few years ago similar to this idea, called <template>
:
<template id="myTemplate">
<div>
My awesome template!
</div>
</template>
To grab our above template and instantiate it, we’d do this in plain JavaScript:
<div id="host"></div>
<script>
let template = document.querySelector('#myTemplate');
let clone = document.importNode(template.content, true);
let host = document.querySelector('#host');
host.appendChild(clone);
</script>
Note how we have id=host
, which is our “host” Node for the template to be injected into.
You may have seen this term floating around Angular in a few ways, such as _nghost
prefixes on Nodes (ng-host) or the host
property in directives.
ngFor and ng-template
First off, <ng-template>
is Angular’s own implementation of the <template>
tag, allowing us to think about application design in web components and the ideas behind them. It also provides us with more power than the <template>
element gives us by default, seamlessly fitting into the way Angular compiles our code.
So how does the above <template>
explanation tell us more about ngFor
and the *
? The asterisk is shorthand syntax for using the <ng-template>
element.
Let’s start from the basic ngFor
example:
<ul>
<li *ngFor="let contact of contacts | async">
<contact-card [contact]="contact"></contact-card>
</li>
</ul>
And demonstrate the <ng-template>
equivalent:
<ul>
<ng-template ngFor let-contact [ngForOf]="contacts | async">
<li>
<contact-card [contact]="contact"></contact-card>
</li>
</ng-template>
</ul>
That’s a lot different! What’s happening here?
When we use *ngFor
, we’re telling Angular to essentially treat the element the *
is bound to as a template.
Angular’s <ng-template>
element is not a true Web Component (unlike <template>
). It merely mirrors the concepts behind it to allow you to use <ng-template>
as it’s intended in the spec. When we compile our code (JiT or AoT), we will see no <ng-template>
elements outputted in the DOM. However, this doesn’t mean we can’t use things like Shadow DOM, as they are still completely possible.
Let’s continue, and understand what ngFor
, let-contact
and ngForOf
are doing above.
ngFor and embedded view templates
First thing’s first, ngFor
is a directive! Let’s check some of the source code:
@Directive({selector: '[ngFor][ngForOf]'})
export class NgForOf<T, U extends NgIterable<T> = NgIterable<T>> implements DoCheck {...}
Here, Angular is using attribute selectors as the value of selector
to tell the @Directive
decorator what attributes to look for.
The directive uses [ngFor][ngForOf]
, which implies there are two attributes as a chained selector. So, how does ngFor
work if we’re not using ngForOf
?
Angular’s compiler transforms any <ng-template>
elements and directives used with a asterisk (*
) into views that are separate from the root component view. This is so each view can be created multiple times.
During the compile phase, it will take let contact of contacts
and capitalise the of
, and create a custom key to create ngForOf
.
In our case, Angular will construct a view that creates everything from the <li>
tag inwards:
<!-- view -->
<li>
<contact-card [contact]="contact"></contact-card>
</li>
<!-- /view -->
It also creates an invisible view container to contain all instances of the template, acting as a placeholder for content. The view container Angular has created essentially wraps the “views”, in our case this is just inside the <ul>
tags. This houses all the templates that are created by ngFor
(one for each row).
A pseudo-output might look like this:
<ul>
<!-- view container -->
<!-- view -->
<li>
<contact-card [contact]="contact"></contact-card>
</li>
<!-- /view -->
<!-- view -->
<li>
<contact-card [contact]="contact"></contact-card>
</li>
<!-- /view -->
<!-- view -->
<li>
<contact-card [contact]="contact"></contact-card>
</li>
<!-- /view -->
<!-- /view container -->
</ul>
ngFor
creates an “embedded view” for each row, passing through the view it has created and the context of the row (the index and the row data). This embedded view is then inserted into the view container. When the data changes, it tracks the items to see if they have moved. If they’ve moved, instead of recreating the embedded views, it moves them around to be in the correct position, or destroys them if they no longer exist.
Context and passing variables
The next step is understanding how Angular passes the context to each <contact-card>
:
<ng-template ngFor let-contact [ngForOf]="contacts | async">
<li>
<contact-card [contact]="contact"></contact-card>
</li>
</ng-template>
So now we’ve understood ngFor
and ngForOf
, how does Angular associate let-contact
with the individual contact
that we then property bind to?
Because let-contact
has no value, it’s merely an attribute, this is where Angular provides an “implied” value, or $implicit
as it’s called under-the-hood.
Whilst Angular is creating each ngFor
item, it uses an NgForOfContext
class alongside an EmbeddedViewRef
, and passes these properties in dynamically. Here’s a small snippet from the source code:
changes.forEachIdentityChange((record: any) => {
const viewRef = >this._viewContainer.get(record.currentIndex);
viewRef.context.$implicit = record.item;
});
Alongside this section of code, we can also see how our aforementioned index
and count
properties are kept updated:
for (let i = 0, ilen = this._viewContainer.length; i < ilen; i++) {
const viewRef = >this._viewContainer.get(i);
viewRef.context.index = i;
viewRef.context.count = ilen;
}
You can dig through the directive source code in more detail here.
This is how we can then access the index
and count
like this:
<ul>
<ng-template ngFor let-i="index" let-c="count" let-contact [ngForOf]="contacts | async">
<li>
<contact-card [contact]="contact"></contact-card>
</li>
</ng-template>
</ul>
Note how we’re supplying let-i
and let-c
values which are exposed from the NgForRow
instance, unlike let-contact
.