This guide was written for Angular 2 version: 2.0.0-rc.4
The ng-repeat
directive in Angular 1.x allows us to iterate over a collection of data and print out DOM nodes that respond to that data. If the data changes, the DOM changes as well. In this guide we'll be converting an Angular 1.x ng-repeat
directive across to Angular 2's ngFor
directive.
In Angular 1.x, using ng-repeat
is pretty simple, we pass the directive some data and it automagically renders out for us. Let's take a look!
Before we can get the ng-repeat
directive working, we need some data inside a controller
bound to the component
:
const app = {
controller() {
this.groceries = [{
id: 0, label: 'Butter'
},{
id: 1, label: 'Apples'
},{
id: 2, label: 'Paprika'
},{
id: 3, label: 'Potatoes'
},{
id: 4, label: 'Oatmeal'
},{
id: 5, label: 'Spaghetti'
},{
id: 6, label: 'Pears'
},{
id: 7, label: 'Bacon'
}];
}
};
angular
.module('app')
.component('app', app);
Next up, we can create some methods to the controller
and assign the template
with an unordered list to make way for our ng-repeat
and upcoming click functions:
const app = {
template: `
<div>
Grocery selected: {{ $ctrl.selectedGrocery.label }}
<ul>
<li>
<a href=""></a>
</li>
</ul>
</div>
`,
controller() {
this.groceries = [{...}];
this.selectGrocery = (grocery) => {
this.selectedGrocery = grocery;
};
this.selectGrocery(this.groceries[0]);
}
};
Then we need to assign ng-repeat
to the <li>
that serves as the template to be cloned for each item in the dataset, followed by an ng-click
to pass each grocery
into the selectGrocery
method:
const app = {
template: `
<div>
Grocery selected: {{ $ctrl.selectedGrocery.label }}
<ul>
<li ng-repeat="grocery in $ctrl.groceries">
<a href="" ng-click="$ctrl.selectGrocery(grocery);">
{{ grocery.label }}
</a>
</li>
</ul>
</div>
`,
...
};
That's it for rendering with ng-repeat
. Let's take a look at $index
and the track by
expression.
The $index
property is automatically provided to us on each ng-repeat
's $scope
object. We can print out each index for the collection with ease:
const app = {
template: `
...
<li ng-repeat="grocery in $ctrl.groceries">
<a href="" ng-click="$ctrl.selectGrocery(grocery);">
{{ grocery.label }} {{ $index }}
</a>
</li>
...
`,
...
};
If you've noted already, each object inside the this.groceries
array has an id
property, which, in this case, indicates that these are unique properties sent back from the server. These unique keys allow us to use the track by
clause inside an ng-repeat
to prevent Angular re-rendering an entire collection.
What it does instead is cleverly only re-render the DOM nodes that require rendering again, rather than destroying and recreating the DOM tree each time. It's simple to use and works as an extension to ng-repeat
's value:
const app = {
template: `
...
<li ng-repeat="grocery in $ctrl.groceries track by grocery.id">
<a href="" ng-click="$ctrl.selectGrocery(grocery);">
{{ grocery.label }} {{ $index }}
</a>
</li>
...
`,
...
};
So you can see here that we've added track by grocery.id
at the end of the repeat syntax. We can also use track by $index
as well. The ng-repeat
directive also exposes $first
, $middle
, $last
, $even
and $odd
properties - see the documentation for more.
You can also pass in a tracking function:
const app = {
template: `
...
<li ng-repeat="grocery in $ctrl.groceries track by trackByGrocery(grocery)">
<a href="" ng-click="$ctrl.selectGrocery(grocery);">
{{ grocery.label }} {{ $index }}
</a>
</li>
...
`,
...
};
The Angular 2 implementation of the ng-repeat
is called ngFor
, purposely in camelCase. The syntax is pretty similar, whereby we can iterate over a collection. Angular 2 uses of
instead of in
with ngFor
to align with the ES2015 for...of
loop.
Assuming we use the same data as in the Angular 1.x example, we can declare this.groceries
in the class constructor:
interface Grocery {
id: number;
label: string;
}
export default class App {
public groceries: Grocery[];
constructor() {
this.groceries = [{
id: 0, label: 'Butter'
},{
id: 1, label: 'Apples'
},{
id: 2, label: 'Paprika'
},{
id: 3, label: 'Potatoes'
},{
id: 4, label: 'Oatmeal'
},{
id: 5, label: 'Spaghetti'
},{
id: 6, label: 'Pears'
},{
id: 7, label: 'Bacon'
}];
this.selectGrocery(this.groceries[0]);
}
selectGrocery(grocery: Grocery) {
this.selectedGrocery = grocery;
}
}
Then bind ngFor
as follows, declaring block scoping with let
:
@Component({
selector: 'my-app',
template: `
<div>
Grocery selected: {{ selectedGrocery.label }}
<ul>
<li *ngFor="let grocery of groceries;">
<a href="#" (click)="selectGrocery(grocery);">
{{ grocery.label }}
</a>
</li>
</ul>
</div>
`
})
export default class App {...}
Nice and easy. What is the leading *
infront of *ngFor
you might ask? It's essentially sugar syntax for using <template>
elements. Check out this section of the documentation for more details.
Instead of $index
(in Angular 1.x) being readily available in the template, we need to actually assign it a variable before we use it:
@Component({
selector: 'my-app',
template: `
<div>
Grocery selected: {{ selectedGrocery.label }}
<ul>
<li *ngFor="let grocery of groceries; let i = index;">
<a href="#" (click)="selectGrocery(grocery);">
{{ grocery.label }} {{ i }}
</a>
</li>
</ul>
</div>
`
})
export default class App {...}
There's a change from Angular 1.x whereby using an object form with track by X
is no longer allowed - it must be a function. So we'll add trackByGrocery
to the App
class (arguments are automatically provided):
@Component({
selector: 'my-app',
template: `
<div>
Grocery selected: {{ selectedGrocery.label }}
<ul>
<li *ngFor="let grocery of groceries; let i = index; trackBy: trackByGrocery;">
<a href="#" (click)="selectGrocery(grocery);">
{{ grocery.label }} {{ i }}
</a>
</li>
</ul>
</div>
`
})
export default class App {
...
trackByGrocery: (index: number, grocery: Grocery): number => grocery.id;
...
}
Altogether now:
import {Component} from '@angular/core';
interface Grocery {
id: number;
label: string;
}
@Component({
selector: 'my-app',
template: `
<div>
Grocery selected: {{ selectedGrocery.label }}
<ul>
<li *ngFor="let grocery of groceries; let i = index; trackBy: trackByGrocery;">
<a href="#" (click)="selectGrocery(grocery);">
{{ grocery.label }} {{ i }}
</a>
</li>
</ul>
</div>
`
})
export default class App {
public groceries: Grocery[];
constructor() {
this.groceries = [{
id: 0, label: 'Butter'
},{
id: 1, label: 'Apples'
},{
id: 2, label: 'Paprika'
},{
id: 3, label: 'Potatoes'
},{
id: 4, label: 'Oatmeal'
},{
id: 5, label: 'Spaghetti'
},{
id: 6, label: 'Pears'
},{
id: 7, label: 'Bacon'
}];
this.selectGrocery(this.groceries[0]);
}
selectGrocery(grocery: Grocery) {
this.selectedGrocery = grocery;
}
trackByGrocery: (index: number, grocery: Grocery): number => grocery.id;
}
Todd Motto (@toddmotto) is a Google Developer Expert from England, UK. He's taught millions of developers world-wide through his blogs, videos, conferences and workshops. He focuses on teaching Angular and JavaScript courses over on Ultimate Courses.