Skip to main content
Ben Nadel at cf.Objective() 2014 (Bloomington, MN) with: Steve 'Cutter' Blades
Ben Nadel at cf.Objective() 2014 (Bloomington, MN) with: Steve 'Cutter' Blades

Sanity Check: Nested Templates Maintain Lexical Binding In Angular 7.2.13

By
Published in Comments (4)

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!

Want to use code from this post? Check out the license.

Reader Comments

447 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??

15,841 Comments

@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: www.bennadel.com/blog/3076-creating-an-index-loop-structural-directive-in-angular-2-beta-14.htm

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel