简体   繁体   中英

What gives the context to ngTemplate in case of *ngFor?

I had some confusion regarding ngTemplateOutletContext , specifically how it is passed to ngTemplate or who passes it to ngTemplate which surrounds the element on which *ngFor was applied. The confusion builds up when I compare it to the way, when we explicitly pass which ngTemplate to render.

Here is what I think happens when we explicitly pass the template -

Say we have an ngContainer / ngTemplate / ( Literally any HTML element as ngTemplateOutlet & ngTemplateOutletContext can be applied to any element) , Now we chose to explicitly provide a template which we have created earlier. Like this -

<ng-template #temp let-number>
  <p>{{number}}</p>
</ng-template>

And we pass it to some element on which we have applied ngTemplateOutlet and ngTemplateOutletContext like this -

<div [ngTemplateOutlet]="temp" [ngTemplateOutletContext]="{$implicit: 4}">
</div>

(We should have used ng-container for the above operation as it won't be in the DOM, but it doesn't concern me here.)

Now the temp ngTemplate will be there having context as what was passed by the div ( or any other parent container ) like this -

在此处输入图像描述

What I understand here is we stamp out the ng-template provided in the ngTemplateOutlet having context passed in ngTemplateOutletContext . We create local variables to use in the template itself.


THE PROBLEM


When we are using *ngFor like -

<p *ngFor="let number of data">
  {{number}}
</p>

and it de-sugars it the following way -

<ng-template #temp ngFor [ngForOf]="data" let-number>
  <p>{{number}}</p>
</ng-template>

where data variable in ts file is data = [1,2,3]

The question is -

1.) Who provides the values ( context ) to this de-sugared ng-template so that it is able to bind let-number to the $implicit properties. I looked into ngForOfContext , there I found that some variable are provided that's why we are able bind variable like even , index like -

<p *ngFor="let number of data; let i=index; let isEven=even">

but who provides these variables in this case to the ng-template . ( When we explicitly pass template, the parent container ( div here) provided the context as we saw earlier )

2.) When we set ngForTemplate it expects a template of type TemplateRef as given here-

在此处输入图像描述

Using p with the *ngFor Directive allows it, but setting a reference of p explicitly when using de-sugared version of ngFor disallows it ie -

<ng-template #temp ngFor [ngForOf]="data" [ngForTemplate]="pTemplate" let-number>

where pTemplate is -

<p #pTemplate *ngFor="let number of data; let i=index; let isEven=even">
  {{number}} {{i}} {{isEven}}
</p>

but it doesn't recognise ptemplate in ngForTemplate . If it doesn't recognise pTemplate as a valid ngForTemplate , how is it able to stamp out p 's in *ngFor version of it?

PS: If I am incorrect in my understanding of ng-template before the questions, I will be more than happy to correct that as well.

When we use the ngFor directive like:

<ng-container *ngFor="let number of data;let i = index;let isEven=even">
    <p [class.even]="isEven">{{i}} : {{number}}</p>
</ng-container>

The inner html is an ng-template. The ngFor directive provides the context to this template for each iteration of the loop.

As you noted there's a ngForTemplate input on the ngFor directive. We can use it to set a template declared outside of the ngFor inner Html

<ng-template #pTemplate let-number let-isEven="even" let-i="index">
    <p [class.even]="isEven">{{i}} : {{number}}</p>
</ng-template>


<ng-container *ngFor="let number of data;let i = index;let isEven=even;template: pTemplate">
</ng-container>

So the ngFor directive provides the context to this template. This context has the type ngForOfContext

We can use this template with an ngTemplateOutlet by providing the context ourself:

<ng-container *ngTemplateOutlet="pTemplate;context:{$implicit:43,even:true,index:2}">
</ng-container>

But the *ngFor syntax is a syntaxic sugar for an ngTemplate. We can use it without the sugar like this:

<ng-template ngFor [ngForOf]="data"  let-number let-isEven="even" let-i="index">
    <p [class.even]="isEven">{{i}} : {{number}}</p>
</ng-template>

Here the ng-template is still the template applied for each iteration. We can also use it in a ngTemplateOutlet by providing a context:

<ng-template #templ ngFor [ngForOf]="data"  let-number let-isEven="even" let-i="index">
    <p [class.even]="isEven">{{i}} : {{number}}</p>
</ng-template>

<ng-container *ngTemplateOutlet="templ;context:{$implicit:43,even:true,index:2}">
</ng-container>

But if use the de-sugared syntax with the ngForTemplate input we have to declare a 2nd ng-template element. Because if we try to apply the ngFor directive without the * on anything else than an ng-template we get an error.

If we look at the source code of the ngFor directive We can see that it requires a templateRef in it's constructor. But the value of this template ref is also set by the ngForTemplate input.

So we need to write

<ng-template #pTemplate let-number let-isEven="even" let-i="index">
    <p [class.even]="isEven">{{i}} : {{number}}</p>
</ng-template>

<ng-template ngFor [ngForOf]="data" [ngForTemplate]="pTemplate"></ng-template>

<!-- And we cannot write : -->
<ng-container ngFor [ngForOf]="data" [ngForTemplate]="pTemplate"></ng-container>

In this case we have 2 templates, but only one is used and so the template on which we put the ngFor doesn't need a context, its only purpose it to be able to call the ngFor directive.

Hope this answers your questions.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM