Angular

Angular NgFor, ng-template

In this post you’re going to learn how to use Angular’s NgFor directive to loop over data to render data or components....

Written by Luci · 12 min read >

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 inverts this.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 ngForlet-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.

Written by Luci
I am a multidisciplinary designer and developer with a main focus on Digital Design and Branding, located in Cluj Napoca, Romania. Profile

Angular Basics: The CLI and Components

Luci in Angular
  ·   7 min read
0 0 votes
Article Rating
Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x