14

I wonder how does Angular's *ngFor directive actually work under the hood? I would like to know the whole process that happens when I use the directive.

For downvoters: I've seen the ng-for-of file, although there is no single usage of passed to *ngFor array's e.g. join() method that I know is invoked. Thanks for your support :) Here is the plunker that shows the behavior: https://plnkr.co/edit/IXVxWrSOhLBgvSal6PWL?p=preview

Daniel Kucal
  • 8,684
  • 6
  • 39
  • 64
  • 4
    https://github.com/angular/angular/blob/master/packages/common/src/directives/ng_for_of.ts – eko Jul 07 '17 at 12:09
  • See also https://angular.io/guide/structural-directives#inside-ngfor, which explains it in the context of the template syntax. – jonrsharpe Jul 07 '17 at 12:11
  • @echonax, @jonrsharpe, thanks guys, although it doesn't explain why e..g `join()` method is invoked on array passed to `*ngFor`. Please take a look at included plnkr, thanks! – Daniel Kucal Jul 07 '17 at 12:49
  • Check this example https://jsfiddle.net/x1jyfuck/. There is nothing to do with angular. You should be careful when write such code – yurzui Jul 07 '17 at 13:13
  • Hey @yurzui, this is very interesting! Could you explain why join() is looped in your example? – Daniel Kucal Jul 07 '17 at 13:22

1 Answers1

46

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.

dlporter98
  • 1,590
  • 1
  • 12
  • 18
Max Koretskyi
  • 101,079
  • 60
  • 333
  • 488