Skip to main content
Ben Nadel at cf.Objective() 2013 (Bloomington, MN) with: Ben Koshy and Ben Arledge
Ben Nadel at cf.Objective() 2013 (Bloomington, MN) with: Ben Koshy ( @animexcom ) Ben Arledge ( @BenArledge )

Template vs. ng-container For Grouping DOM Nodes In Angular 2.4.4

By on

Yesterday, when I wrote about mixing specific and non-specific ng-content directives when transcluding content in Angular 2, you may have noticed that my demo code used an ng-container directive. I only just discovered the ng-container directive as I was learning up on ng-content. The ng-container directive is a way to group a set of DOM (Document Object Model) nodes when you don't want to render the container element itself (ie, just the children). Historically, that's exactly what I've been using the Template directive for; so, I wanted to shallow-dive in and a take a look at what differentiates the Template directive from the ngContainer directive.

Run this demo in my JavaScript Demos project on GitHub.

The most immediately apparent difference between template and ng-container is the syntax that can be used with structural directives. When you use the *-notation with a structural directive, it actually "de-sugars" the current element into a template that contains the given element.

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

... becomes:

<template [ngIf]="true"> <div> ... </div> </template>

This means that if you are explicitly using the template directive with a structural directive (ex, ngIf), you have to use the already-de-sugared syntax. This constrasts with the ng-container directive that "groups" like a template, but allows you to use the sugary syntax:

<ng-container *ngIf="true"> <div> ... </div> </ng-container>

But, the ng-container directive isn't just a short-cut for a template directive that allows you to use the sugary structural directive syntax - it has different behavior; though, the behaviors appear to overlap largely in their visual output.

The most obvious difference in behavior can be demonstrated if you try and use the template and ng-container directives on their own, without any structural directive:

<template>
	<p>
		In template, no attributes.
	</p>
</template>

<ng-container>
	<p>
		In ng-container, no attributes.
	</p>
</ng-container>

Here, we're just grouping DOM nodes with each directive - no conditional rendering. And, when we run this code, we get the following page output:

Template vs. ng-container in Angular 2.

As you can see, only the ng-container actually rendered anything. This is because the template directive, on its own, doesn't do anything but create a TemplateRef intended to be consumed by another piece of rendering logic. So, if there's nothing that explicitly renders the TemplateRef, nothing gets output to the view. That said, you can see that there is no "<ng-container>" element - the ng-container directive, like the template, only renders its children.

Of course, we're only going to be using the template or ng-container directives if we want to conditionally render something (otherwise we'd omit the parent element altogether and just inline the children). So, let's see what happens when we throw an ngIf structural directive into this demo:

<template [ngIf]="true">
	<p>
		ngIf with a template.
	</p>
</template>

<ng-container *ngIf="true">
	<p>
		ngIf with an ng-container.
	</p>
</ng-container>

Again, we can clearly see the syntactic difference between the two use-cases. And, this time, when we run the code, we get the following output:

Template vs. ng-container in Angular 2.

As you can see, both the template and the ng-container directives rendered their children. By adding the [ngIf] structural directive to the template, it gave the TemplateRef some rendering logic which is why this version produces output. And, if you look at just the visual output, both the template and the ng-container rendered the exact same thing.

But, if you look at the rendered HTML, you can see that the ng-container actually outputs two template comment placeholders / view hooks. That's because the the *ngIf syntax on the ng-container actual de-sugars to a template that contains the given ng-container. We can see this more clearly if we actually compare the ng-container output to a template-wrapped ng-container output. In this case, we'll use a repeater to demonstrate the difference in the context of a structural directive:

<p>
	Repeater:

	<template ngFor let-x [ngForOf]="[ 'A', 'B', 'C' ]" let-ix="index">
		{{ x }}( <span>{{ ix }}</span> )
	</template>
</p>

<p>
	Repeater:

	<ng-container *ngFor="let x of [ 'A', 'B', 'C' ]; let ix = index ;">
		{{ x }}( <span>{{ ix }}</span> )
	</ng-container>
</p>

<p>
	Repeater (showcasing why comments):

	<template ngFor let-x [ngForOf]="[ 'A', 'B', 'C' ]" let-ix="index">
		<ng-container>
			{{ x }}( <span>{{ ix }}</span> )
		</ng-container>
	</template>
</p>

Here, the first two repeaters are just demonstrating the behavior with template vs. ng-container. But, the third repeater explicitly wraps the ng-container in a template, moving the structural directive from the ng-container up to the parent template. And, when we run this code, we get the following output:

Template vs. ng-container in Angular 2.

As you can see, visually speaking, all three of these render the same exact thing (so to speak). But, if we look at the HTML, we can see once again that the ng-container version outputs more HTML comments / view hooks. And, if we compare the 2nd and 3rd repeaters, we can see that they output the same HTML. This is because the 3rd repeater - which is a template wrapping an ng-container - more explicitly demonstrates the interaction between the ng-container directive and any structural directives that may be attached to it.

Both the Template directive and the ng-container directive share some behavior - they both wrap content; and, when rendered, the both omit themselves, rendering only their children. But, they are different mechanism; one (ng-container) is not just a syntax-alternative to the other (template). Ultimately, they present different behaviors and have different life-cycles. And, they can each be used in different ways within an Angular 2 application.

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

Reader Comments

15,643 Comments

@Shlomi,

I am not sure I understand what you mean? Are you saying that the short-hand syntax, ex "*ngIf" can be used directly with <template> elements in NG 4? I'm still trying to learn NG 2 - I haven't looked at any of the future versions yet :P

7 Comments

For my future self, or anybody else that didn't understand the point right away... (and maybe Ben can confirm Im getting it right):
The difference between using ng-container and just using a div (or any other element) is that it doesn't actually output/render any dom, while keeping the grouping of the elements in the template.

15,643 Comments

@Filip,

That's exactly right - sorry if I wasn't making that clear in my writing. I think I was perhaps leaning too heavily on the HTML output to do the explaining for me :( But yes, you are exactly right. If you use:

<ng-container><div>Content</div></ng-container>

... the actual rendered HTML is:

<div>Content</div>

... notice no ng-container element.

Now, with a single root-element (DIV), it doesn't really help because you can always attach structural directives directly to the root-element. Making, these two things functionality similar (though not exactly the same):

<ng-container *ngIf="true"><div>Content</div></ng-container>

<div *ngIf="true">Content</div>

Both of the above will conditionally render the DIV element, so the ng-container has no value-add. The real value-add is when you want to group several top-level DOM nodes without adding an extra "parent" element:

<ng-container *ngIf="true">
<div>Content A</div>
<div>Content B</div>
</ng-container>

... which will output both top-level DIVs.

Ultimately, however, I still prefer the <template> approach to the same problem (which requires a slightly different syntax):

<template [ngIf]="true">
<div>Content A</div>
<div>Content B</div>
</template>

... but, to each their own :D

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