How is Angular’s *ngFor loop implemented?

Here is a high level overview. Suppose you define your template like this:

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

Then it’s transformed to the following by the compiler:

<ng-template let-item [ngForOf]="items">
    <span>{{item}}</span>
</ng-template>

Then Angular applies ngForOf directive to the template element. Since this directive’s host element is template, it injects the templateRef. It also injects the viewContainerRef that acts as an anchor element and will be used to add DOM elements alongside:

  constructor(
       private _viewContainer: ViewContainerRef, 
       private _template: TemplateRef<NgForOfContext<T>>,

The directive defines ngForOf as an input and then waits until it’s initialized and creates a differ:

  ngOnChanges(changes: SimpleChanges): void {
      const value = changes['ngForOf'].currentValue;
          this._differ = this._differs.find(value).create(this.ngForTrackBy);

Then on each check detection cycle it compares the values to the previous values using this differ:

  ngDoCheck(): void {
    if (this._differ) {
      const changes = this._differ.diff(this.ngForOf);
      if (changes) this._applyChanges(changes);
    }
  }

If the values changed, it applies the changes doing the the following things:

1) generates embedded view context for each item in items

context = new NgForOfContext<T>(null !, this.ngForOf, -1, -1)

2) creates embedded view with this context using the templateRef which effectively renders new value in the DOM

this._viewContainer.createEmbeddedView(
                this._template, context , currentIndex);

3) adds relevant values to context

  viewRef.context.index = i;
  viewRef.context.count = ilen;
  viewRef.context.$implicit = record.item;`

Now, your question:

although it doesn’t explain why e..g join() method is invoked on array
passed to

It’s called by the function normalizeDebugBindingValue here because your application is running in the development mode:

function normalizeDebugBindingValue(value: any): string {
  try {
    // Limit the size of the value as otherwise the DOM just gets polluted.
    return value != null ? value.toString().slice(0, 30) : value;
                           ^^^^^^^^^^^^^^^
  } catch (e) {
    return '[ERROR] Exception while trying to serialize the value';
  }
}

If you enable production mode, this function will no longer be called. Check the plunker.

Leave a Comment