Angular 2: ngFor over a series of elements

Angular 1 supported the ability to repeat a series of elements using ngRepeat through the use of special start and end points named ng-repeat-start and ng-repeat-end.

<header ng-repeat-start="item in items">  
  {{item.header}}
</header>  
<div>  
  {{item.content}}
</div>  
<footer ng-repeat-end>  
  {{item.author}}
</footer>  

The ability to iterate over a series of elements meant the ngRepeat was not limited to repeating only over one element. Iterating over a series of elements allowed multiple top level elements and when desired, permitted the selection of a single top-level element from several elements. Consider the following example:

<tr ng-repeat="row in rows">  
  <td ng-if="row.id === editRowId">
    <input type="text" ng-model="row.field">
  </td>
  <td ng-if="row.id !== editRowId">
    {{row.field}}
  </td>
</tr>  

The above code requires an ng-if on each column to determine which kind of column to display: an edit column or a view column. Beyond the annoyance of typing ng-if over and over again, this code is extremely inefficient. Each ng-if must evaluate the expression, the DOM must be manuipulated for each column, and each ng-if creates a new scope object. Instead of using this inefficient solution, a better solution employs start and end points.

<tr ng-repeat-start="row in rows" ng-if="row.id === editRowId">  
  <td>
    <input type="text" ng-model="row.field">
  </td>
</tr>  
<tr ng-repeat-end ng-if="row.id !== editRowId">  
  <td>
    {{row.field}}
  </td>
</tr>  

The above code is more efficient in that the editRowId expression is evaluated only twice per row (as opposed to twice per column for each row), there are significantly fewer DOM manipulations and significantly fewer scope objects created.

Angular 2: ngFor replaces ngRepeat

With the release of Angular 2, ngRepeat from Angular 1 has been replaced with ngFor. While the syntax has changed, the overall purpose of both is the same: to repeat an element based on model data.

<ul>  
  <li *ngFor="let item of items">
    {{item}}
  <li>
</ul>  

Observe the code above, the name of the directive has changed from ngRepeat to ngFor. The directive marker is applied to the DOM using camel case syntax instead of dashed syntax. The operator in has been replaced with of to follow the convention of ES2015 iterating over some iterable such as an Array, Map, Set, String, etc... Also following the conventions of ES2015, the let syntax defines the iteration variable to be mutable. Finally, the directive marker, ngFor, is prefixed with an asterisk. We will come back to that in a moment.

ngFor over a series of elements

Returning to the original issue, how does ngFor iterate over a series of elements? In Angular 2, there are no special endpoints. There is no ngForStart or ngForEnd. The key to solving this problem is the asterisk and the syntax which hides behind it. The asterisk indicates that ngFor is a structural directive which means it does DOM manipulation such as adding and removing elements. The asterisk syntax is shorthand notation which fully expands into the template tag. The template tag serves as a container to repeat the series of elements contained within it.

<ul>  
  <template ngFor let-item [ngForOf]="items">
    <li>
      {{item}}
    <li>
  </template>
</ul>  

The expanded syntax contains the additional attribute directive ngForOf which accepts the iterable to be iterated over.

Now the logic to repeat a single element or even a series of elements can be expressed in the content of the template tag. Using the Angular 1 code from above, review the code below.

<template ngFor let-row [ngForOf]="rows">  
  <tr *ngIf="row.id === editRowId">
    <td>
      <input type="text" ng-model="row.field">
    </td>
  </tr>
  <tr *ngIf="row.id !== editRowId">
    <td>
      {{row.field}}
    </td>
  </tr>
</template>  

The expanded template tag syntax of Angular 2 achieves the same Angular 1 benefits of iterating over a series of elements.

A final comment

The template tag is part of HTML5 and is included in the specifications associated with web components. From a pure HTML5 perspective, the primary purpose of the template tag is to define an HTML document fragment which is parsed but not rendered. When the template tag content is cloned and added to a parent element other than the template tag itself, it will be rendered. While some web browsers do not directly support the features of the template tag now (Angular 2 provides the code to handle it), they will in the future, and becoming familiar with this tag and the many other aspects of web component development is essential to embracing the future of user interface development.