Exploring ng-template, ng-container, and ngTemplateOutlet in Angular

Angular Core ships with a handful of directives that unlock some of its most interesting capabilities. You have likely encountered ng-template before, even if only in passing — for instance, when using ngIf with an else branch, or while working with ngSwitch.

Both ng-template and its companion ngTemplateOutlet are far more than simple structural niceties. They enable a broad spectrum of advanced patterns. Because these directives are almost always used in tandem with ng-container, it makes sense to study them together. Seeing how they interact gives each one more context and makes the whole picture easier to grasp.

Let's walk through some of the more powerful use cases these tools open up. The complete source code for the examples discussed here is available in this Github repository.

What We Will Cover

The following topics are on the agenda for this guide:

  • A first look at the ng-template directive
  • How Template Input Variables work
  • Using ng-template in combination with ngIf
  • The de-sugared form of ngIf and its relation to ng-template
  • Template references and the TemplateRef injectable
  • Building Configurable Components through Template Partial @Inputs
  • The ng-container directive and the right time to use it
  • Rendering Dynamic Templates with ngTemplateOutlet
  • Passing @Input Properties through the Template Outlet
  • Bringing Everything Together in a Combined Example
  • Final Thoughts and Key Takeaways

Getting Acquainted with the ng-template Directive

As its name suggests, ng-template is Angular's way of defining a template. The content inside this tag represents a fragment of markup that can later be combined with other templates to build the final view for a component.

Angular itself relies on ng-template internally. The structural directives we use constantly — ngIf, ngFor, and ngSwitch — all depend on it behind the scenes.

The best way to start is with a practical example. The code below shows the skeleton of a tab component, with two buttons defined for the tab headers:

The First Surprise with ng-template

When you run the snippet above, the expected result might not appear. In fact, the most likely outcome is that nothing is rendered on screen at all.

That behavior is entirely by design. With ng-template, you are simply declaring a template — nothing more. It doesn't get displayed until you actually use it somewhere.

Let's find a scenario where we can see some output, by turning to some of the most commonly used directives in Angular.

The ng-template directive in an ngIf context

Most developers meet ng-template for the first time when implementing an if/else pattern. A typical case looks like this:

This is a classic use of the ngIf/else construct: we show a fallback loading template while we wait for data to come in from the server.

Notice that the else clause targets a template named loading. That name comes from a template reference, attached via the #loading syntax.

However, that else template is only half the story. Using ngIf actually creates a second, implicit ng-template as well. Here is what goes on under the hood:

The above is what Angular produces internally when it de-sugars the concise *ngIf syntax. Let's examine what that transformation involves:

  • The element that originally carried the structural directive gets wrapped inside an ng-template.
  • The expression from *ngIf is broken apart and assigned to two separate directives — [ngIf] and [ngIfElse] — using the template input variable syntax.

That's just one specific example involving ngIf. A comparable process unfolds when ngFor and ngSwitch are used.

Given how ubiquitous these directives are, it means templates are everywhere in Angular, whether they are written explicitly in the markup or generated implicitly by the compiler.

With that foundation in place, a natural question arises:

What happens if you try to apply multiple structural directives to the same element?

Dealing with Multiple Structural Directives

Consider the scenario where you attempt to use ngIf and ngFor together on one element:

That approach fails. Instead, Angular throws the following error:

Uncaught Error: Template parse errors:
Can't have multiple template bindings on one element. Use only one attribute 
named 'template' or prefixed with *

The issue is clear: applying two structural directives to a single element is not permitted. The typical workaround looks something like this:

In this version, the ngIf has been relocated to a wrapping div. The solution works, but it requires the introduction of an additional DOM element that exists purely for structural purposes.

The question then becomes: is there a way to attach a structural directive to a block of content without adding that extra element to the page?

The answer is yes, and that's precisely what the ng-container directive enables.

The ng-container Directive

To eliminate the need for a superfluous div, we can turn to ng-container:

As the example shows, ng-container gives us a target for a structural directive that doesn't produce any extra markup in the final DOM.

There is another important role for ng-container: it acts as the ideal placeholder for injecting a template dynamically into the view.

Instantiating Templates Dynamically with ngTemplateOutlet

Being able to reference templates and hand them to directives like ngIf is only scratching the surface.

We have the power to take a template and render it at any point in the view by using the ngTemplateOutlet directive:

This example demonstrates how ng-container helps out: it serves as the spot where the loading template defined earlier gets instantiated.

The loading template is referred to using its template reference #loading, and the ngTemplateOutlet directive is responsible for actually rendering it.

There is no limit to how many ngTemplateOutlet instances we can create, nor to the number of different templates we can display. The value assigned to this directive can be any expression that resolves to a template reference — something we will come back to shortly.

Now that we know how to render templates, the next topic worth exploring is what those templates can actually see and access.

The Context of a Template

A fundamental question about any template is its visibility: what variables are accessible from inside it?

Does a template have its own isolated variable scope, and if so, which variables fall within it?

Within the body of an ng-template, we have access to the same context variables as the surrounding template. The variable lessons from the outer scope, for instance, can be used without issue.

The reason is that every ng-template inherits the context in which it is embedded.

On top of that, each template can declare its own set of input variables. In fact, every template is associated with a context object that holds all of its template-specific input variables.

Let's look at that in action:

Here is the breakdown of what this example is doing:

  • Unlike previous examples, this template declares an input variable (and it could declare several).
  • The input variable is named lessonsCounter, declared using the let- prefix on the ng-template property.
  • Inside the ng-template body, lessonsCounter is accessible; outside of it, the variable no longer exists.
  • Its value comes from the expression bound to let-lessonsCounter.
  • That expression is evaluated against the context object supplied to ngTemplateOutlet when the template is instantiated.
  • For anything to show up inside the template, the context object must contain a property called estimate.
  • The context object is passed via the context property on ngTemplateOutlet, which accepts any expression that evaluates to an object.

With the above example, the resulting output on screen would be:

Approximately 10 lessons ...

That covers the essentials of defining and instantiating custom templates.

But there's another level of control available: interacting with templates programmatically from within the component class. Let's see how that works.

Accessing Templates via References

Just as a template reference like #loading can be used in the markup, a template can also be injected directly into our component using the ViewChild decorator:

The example shows that a template can be injected in much the same way as a DOM element or a component — by supplying the template reference name, defaultTabButtons, to ViewChild.

This opens up a significant capability: templates are reachable from the component class. We can, for example, pass them down to child components as inputs.

Why would we want to do that? One compelling reason is component customization. Instead of only passing configuration parameters or objects, we can pass an entire template as an input.

Configurable Components with Template Partial @Inputs

Imagine a tab container component where we want the consumer to have control over the styling and structure of the tab buttons.

Here's what that would look like. First, we define the custom button template in the parent component:

Next, in the tab container component itself, we declare an input property that is also a template, called headerTemplate:

This combined example contains several moving parts. Let's break it down:

  • A default template, named defaultTabButtons, is defined for the tab buttons.
  • That default is only used if the headerTemplate input remains undefined.
  • Should the property be set, the custom template passed in through headerTemplate takes over button rendering.
  • The header template is instantiated within an ng-container placeholder using the ngTemplateOutlet property.
  • A ternary expression decides between the default and custom templates. For more involved logic, this decision could be delegated to a component method.

The outcome is a flexible tab container: it ships with a sensible default look, but can easily swap in a completely different layout when a custom template is provided.

Summary and Key Takeaways

Together, ng-container, ng-template, and ngTemplateOutlet form a powerful toolkit for building dynamic, highly customizable components.

With input templates, we have the ability to significantly alter a component's appearance and behavior. Templates can be defined once and then instantiated in multiple places throughout the application.

This is only one of the many ways these features can be combined to great effect.

It is my hope that this guide shed some light on the more advanced capabilities of Angular Core. If you have any questions, feel free to leave them in the comments below and I'll get back to you.

For those interested in diving even deeper into Angular Core features, the Angular Core Deep Dive course offers a far more extensive exploration of Angular Templates.

To stay informed about future posts, consider subscribing to our newsletter.

If you're new to Angular and want a solid starting point, take a look at the Angular for Beginners Course:

Angular ng-template, ng-container and ngTemplateOutlet: Guided Tour — figure 1

Further Reading on Angular

You might find the following popular articles interesting as well: