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-templatein combination withngIf - The de-sugared form of
ngIfand its relation tong-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
@InputProperties 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
*ngIfis 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 thelet-prefix on theng-templateproperty. - Inside the
ng-templatebody,lessonsCounteris 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
ngTemplateOutletwhen 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
contextproperty onngTemplateOutlet, 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
headerTemplateinput remains undefined. - Should the property be set, the custom template passed in through
headerTemplatetakes over button rendering. - The header template is instantiated within an
ng-containerplaceholder using thengTemplateOutletproperty. - 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:
Further Reading on Angular
You might find the following popular articles interesting as well:
- Getting Started With Angular - Development Environment Best Practices With Yarn, the Angular CLI, Setup an IDE
- Why a Single Page Application, What are the Benefits ? What is a SPA ?
- Angular Smart Components vs Presentation Components: What's the Difference, When to Use Each and Why?
- Angular Router - How To Build a Navigation Menu with Bootstrap 4 and Nested Routes
- Angular Router - Extended Guided Tour, Avoid Common Pitfalls
- Angular Components - The Fundamentals
- How to build Angular apps using Observable Data Services - Pitfalls to avoid
- Introduction to Angular Forms - Template Driven vs Model Driven
- Angular ngFor - Learn all Features including trackBy, why is it not only for Arrays ?
- Angular Universal In Practice - How to build SEO Friendly Single Page Apps with Angular
- How does Angular Change Detection Really Work ?
