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
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With