Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
Ben Nadel at Scotch On The Rock (SOTR) 2010 (London) with: Greg Franklin
Ben Nadel at Scotch On The Rock (SOTR) 2010 (London) with: Greg Franklin

Sanity Check: Nested Templates Maintain Lexical Binding In Angular 7.2.13

By Ben Nadel on

A couple of years ago, I looked at the Ng-Template construct in Angular 2 RC 1 and demonstrated that templates appear to maintain lexical binding even when passed out-of-scope. And, since that exploration, my mental model for TemplateRef's has been parallel to that of the JavaScript closure. However, the other day, Arul asked me how to access a top-level template variable from within a nested template. According to the rules of lexical binding, this should "just work". But, his question gave me pause; so, I decided to run a quick sanity check on nested template behaviors in Angular 7.2.13. And, from what I can see, nested templates maintain lexical binding perfectly well.


 
 
 

 
 
 
 
 

Run this demo in my JavaScript Demos project on GitHub.

View this code in my JavaScript Demos project on GitHub.

To test the scoping of template variables in a nested template context, I've created a silly little App component that iterates over a list of values. Each NgForOf iteration uses several TemplateRef's, including two nested templates and one sibling template:

  • <ng-template
  • ngFor
  • [ngForOf]="[ 'foo', 'bar', 'bar', 'foo', 'foo', 'bar', 'baz' ]"
  • [ngForTemplate]="ngForTemplateRef">
  • </ng-template>
  •  
  • <!--
  • When passing a TemplateRef to a structural directive, the context variables need to
  • be accessed on the TemplateRef, not on the structural directive. So, for example, the
  • "let-value" here is accessing the $implicit context value from the [ngFor] directive
  • above.
  • -->
  • <ng-template #ngForTemplateRef let-value>
  •  
  • <p>
  • <strong>{{ value }}</strong>
  •  
  • <ng-template
  • [ngIf]="( value === 'foo' )"
  • [ngIfThen]="ngIfThenRef"
  • [ngIfElse]="ngIfElseRef">
  • </ng-template>
  •  
  • <!--
  • Since templates are lexically-bound, they should have access to all template
  • variables declared in-scope, including other TemplateRef values in the parent
  • context. As such, the ngForTemplateRef can reference the sibling template,
  • exclamationRef.
  • -->
  • <ng-template
  • [ngTemplateOutlet]="exclamationRef">
  • </ng-template>
  • </p>
  •  
  • <!--
  • Just as with the "let-value" above, the "let-condition" template variable on
  • the following TemplateRefs are accessing the "$implicit" context value from the
  • [ngIf] directive above.
  •  
  • Since these ng-template's are being declared inside another ng-template, they are
  • lexically-bound to the "let-value" template variable. As such, these templates
  • can access both their local variables (let-condition) as well as the local
  • variables declared by the parent template (let-value).
  • -->
  •  
  • <ng-template #ngIfThenRef let-condition>
  • ( [ {{ condition }} ] Noice, '{{ value }}' is 'foo' )
  • </ng-template>
  •  
  • <ng-template #ngIfElseRef let-condition>
  • ( [ {{ condition }} ] Oh noes, '{{ value }}' is not 'foo' )
  • </ng-template>
  •  
  • </ng-template>
  •  
  • <ng-template #exclamationRef>
  • !!
  • </ng-template>

There's no meaningful business logic here; so, please forgive the "foo", "bar", and "baz" values. The point here is only to see which templates have access to which values. Notice that the both the top-level and nested TemplateRef's are accessing "value". And, that the nested top-level template is also trying to access a sibling TemplateRef:


 
 
 

 
 Looking at nested ng-templates and variable references in Angular 7.2.13. 
 
 
 

Again, there's no business logic here - only an exploration of bindings. And, when we run this Angular app in the browser, we get the following output:


 
 
 

 
 Looking at nested ng-templates and variable references in Angular 7.2.13. 
 
 
 

As you can see, the lexical binding of Ng-Template in Angular "just works". Or, at least, it works much like a JavaScript closure. The only other thing worth pointing out here is that accessing the "context" variables (ex, let-value, let-condition) has to happen on the TemplateRef itself, not on the context in which the TemplateRef is consumed.

Templates in Angular work like lexically-bound HTML fragments. This allows them to access variables declared in a parent scope as well as maintain bindings when passed out-of-scope (such as into another directive). Having nested templates does not change this behavior. Sanity check achieved!



Reader Comments

Ben. Interesting.

?I used ng-template this evening with *ngIf, that evaluates to true. But the HTML content inside did not display. When I used the same *ngIf statement on a parent DIV conainer, the content displayed!

Does ng-template have some special requirement??

Reply to this Comment

@Charles,

Ah, good question. So the * is actually some "syntactic sugar". It allows structural directives to be applied without the use of ng-template. So, the following two are functionally equivalent:

<p *ngIf="true">
	.... 
</p>

... and:

<ng-template [ngIf]="true">
	<p>
		.... 
	</p>
</ng-template>

The difference is that when you remove the *, you have to use underlying syntax, which breaks-apart the * expression and uses property bindings.

This is similar with *ngFor, but easier to see the big difference:

<p *ngFor="let value of values">
	{{ value }}
</p>

... is the same as:

<ng-template ngFor let-value [ngForOf]="values">
	<p>
		{{ value }}
	</p>
</ng-template>

I try to break this syntax translation down in an earlier post, if it is helpful: https://www.bennadel.com/blog/3076-creating-an-index-loop-structural-directive-in-angular-2-beta-14.htm

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
NEW: Some basic markdown formatting is now supported: bold, italic, blockquotes, lists, fenced code-blocks. Read more about markdown syntax »
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.