Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How is Angular's *ngFor loop implemented?

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

like image 377
Daniel Kucal Avatar asked Jul 07 '17 12:07

Daniel Kucal


1 Answers

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.

like image 189
Max Koretskyi Avatar answered Sep 20 '22 15:09

Max Koretskyi