Reputation: 2342
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 i.e. -
<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.
Upvotes: 8
Views: 8942
Reputation: 2898
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.
Upvotes: 6