-->

How is Angular's *ngFor loop implemented?

2019-01-27 09:32发布

问题:

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

回答1:

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 no longer be called, check the plunker.