skip to content

Template Outlets & Containers

<ng-template> holds markup rendered only on demand, <ng-container> groups nodes without a DOM element, and NgTemplateOutlet stamps a template with a context. Interviewers test $implicit.

part ofAngularoverview, primer and where to startread it →
on this pageshow

explore

questions

4

In Angular, what is the difference between <ng-template> and <ng-container>, and what does each leave in the rendered DOM?

level: juniorimportance: must knowfreq 68%

answer

  1. one waits, one groups
  2. TemplateRef versus in-place children
  3. both leave only a comment
  4. bindings on ng-container are ignored

basics

~20 s

<ng-template> declares a fragment that Angular compiles but renders only when an outlet or code stamps it; <ng-container> renders its children immediately without adding a wrapper element. Neither becomes an HTML element itself; each is represented by a comment node.

solid answer

~40 s

`<ng-template>` holds a **template fragment**: nothing inside it renders, no child component is created, until something stamps it with `NgTemplateOutlet` or `ViewContainerRef.createEmbeddedView()`, and each stamp creates a new embedded view. You get it as a `TemplateRef` through a `#ref`, a query or injection. `<ng-container>` renders its children right away but groups them without a real element, which is what you want inside a `<ul>`, a table row or a flex layout, and it is the usual host for an outlet. Neither becomes a real element: each is represented in the DOM by a comment node. Because `<ng-container>` has no element, Angular ignores attribute bindings and event listeners placed on it.

code

html · 14 lines
html
<ul>
  <ng-container>
    <li>Always rendered</li>
    <li>No wrapper element</li>
  </ng-container>
</ul>

<ng-template #emptyState>
  <p>Nothing here yet</p>
</ng-template>

@if (items().length === 0) {
  <ng-container [ngTemplateOutlet]="emptyState" />
}

go deeper

for a junior

Recall the one-line split: ng-template waits to be stamped, ng-container renders now without a wrapper. Mention that neither produces a real element.

for a middle

Explain that a template becomes a TemplateRef, that each stamp creates an embedded view with new child instances, and why bindings on ng-container are ignored.

for a senior

Show when each is the right tool in real markup: tables, lists and layouts where a wrapper breaks structure, and deferring expensive content until it is needed.

for a principal

Frame these as the primitives behind reusable UI: templates as values that components exchange, versus containers as pure layout-neutral grouping.

## Two elements that look alike and do opposite jobs Angular templates have two special elements that never become real HTML elements: **`<ng-template>`** and **`<ng-container>`**. Both disappear from the rendered DOM as elements, and both are left behind only as a **comment node** (in development builds Angular writes readable text into those comments, such as `<!--container-->` and `<!--ng-container-->`; in production builds the comment is empty). The difference is *when their content renders*: - **`<ng-template>`** declares a **template fragment**. Angular compiles it, but renders **nothing** from it until some code or directive explicitly stamps it out. - **`<ng-container>`** renders its children **immediately**, in place, but without wrapping them in an extra element. A useful shorthand: `<ng-template>` is *deferred content*, `<ng-container>` is *invisible grouping*. ## `<ng-template>`: declared now, rendered on demand Content inside `<ng-template>` is inert at first. No child component inside it is constructed, no lifecycle hook runs and no binding is evaluated, because no view exists yet. What Angular gives you instead is a **`TemplateRef`**, a handle to the compiled fragment. You can get one in three ways: 1. put a template reference variable on it (`<ng-template #empty>`) and use that name elsewhere in the same template; 2. query for it from the component class, for example with `viewChild` or `contentChild`; 3. inject `TemplateRef` in a directive applied directly to the `<ng-template>`. Something then has to render it: the **`NgTemplateOutlet`** directive in a template, or `ViewContainerRef.createEmbeddedView()` in code. Each render creates a new **embedded view**, so stamping the same template three times creates three independent copies, each with its own child component instances. Two facts about a fragment are easy to miss. Bindings inside it are evaluated against the **component that declares it**, not the one that renders it. And every `*` structural directive is sugar for an `<ng-template>`: Angular wraps the element in one behind the scenes. ## `<ng-container>`: a grouping node with no element `<ng-container>` is for the times you need a *place* in the template, for a directive, an outlet or a group of siblings, but adding a `<div>` would break the markup: inside a `<ul>`, a `<tr>`, a flex or grid layout, or a `<select>`. ```html <ul> <ng-container> <li>First</li> <li>Second</li> </ng-container> </ul> ``` renders as a `<ul>` with two `<li>` children and a comment, with no wrapper element between them. Typical uses: - hosting `NgTemplateOutlet` or `NgComponentOutlet` so the rendered content lands without an extra element; - applying a directive to a group of siblings, which descendants can then inject; - grouping several elements under one legacy structural directive. ## Side by side | | `<ng-template>` | `<ng-container>` | |---|---|---| | Content rendered | Only when stamped by an outlet or `createEmbeddedView()` | Immediately, in place | | DOM output | A comment anchor, nothing else until rendered | Its children plus a comment node | | Child components created | On each stamp, a fresh instance per render | Once, like any normal content | | Gives you | A `TemplateRef` | A grouping location | | Typical partner | `NgTemplateOutlet`, `ViewContainerRef` | Outlets, directives, grouping | ## What `<ng-container>` does not do Because there is no element, there is nothing to bind to. The Angular guide states that **attribute bindings and event listeners on `<ng-container>` are ignored**, including ones a directive tries to apply through its host. So `<ng-container class="row" (click)="select()">` produces neither a class nor a click handler. If you need styling or events, use a real element. Directives themselves still work on it, and descendants can inject them. ## How to answer it in an interview A crisp answer covers four points in order: 1. **Rendering time.** `<ng-template>` renders only when stamped; `<ng-container>` renders immediately. 2. **DOM footprint.** Neither adds an element; each is represented by a comment node. 3. **What you get.** A template gives you a `TemplateRef` you can pass around, even as an input to another component; a container gives you a location. 4. **Limits.** A container cannot carry classes, styles or listeners, and a template renders nothing until something asks for it. Interviewers often follow up with a debugging twist: "my component inside `<ng-template>` never initialises". The answer is that nothing references the template yet, so no view, and therefore no component instance, exists. ## In current Angular Since v17 the built-in `@if`, `@for` and `@switch` blocks are not elements at all, so many `<ng-container *ngIf>` wrappers written for older code are no longer needed; `*ngIf`, `*ngFor` and `*ngSwitch` are deprecated since v20. `<ng-template>` and `<ng-container>` themselves are not deprecated: they remain the standard way to hold a reusable fragment and to host an outlet.

  • Is a component placed inside an <ng-template> constructed when the parent component initialises?
    No. Until the fragment is stamped there is no view, so no child component is constructed and none of its lifecycle hooks run. Each stamp by `NgTemplateOutlet` or `createEmbeddedView()` creates a fresh instance, so stamping twice gives two independent components.
  • Why does `<ng-container class="row" (click)="select()">` do nothing?
    `<ng-container>` has no DOM element, only a comment node, so there is nothing to put a class on or listen to. Angular ignores attribute bindings and event listeners on it, including ones a directive applies through its host. Use a real element when you need styling or events.
  • Where do bindings inside an <ng-template> get their values from?
    From the component that declares the `<ng-template>`, regardless of where it is rendered, plus any values passed in the context object when it is stamped. The component rendering it contributes nothing unless it passes it through the context.

An <ng-template> is a rubber stamp in a drawer: nothing appears on paper until someone presses it, and every press makes a new copy. An <ng-container> is a paper clip: it holds sheets together without adding a sheet of its own.

saying these in an interview costs you the question

  • Thinks <ng-template> content renders in place like a normal div
  • Believes <ng-container> produces a div or span in the DOM
  • Expects a class or click binding on <ng-container> to work
  • Thinks components inside <ng-template> are created eagerly but hidden
  • Believes <ng-template> was deprecated along with *ngIf
open as a page

In Angular, how do you pass values into a template rendered by NgTemplateOutlet, and what does the $implicit context key do?

level: middleimportance: must knowfreq 58%

basics

~10 s

Bind an object to ngTemplateOutletContext (or write context: in the * form); the template reads its keys with let-name="key". $implicit is the default key: a let- declaration with no value receives it.

open as a page

How would you let consumers of an Angular data-table component supply their own cell template, and whose data and providers does that template see?

level: seniorimportance: should knowfreq 45%

basics

~10 s

Accept a TemplateRef (input or content query) and render it per row with NgTemplateOutlet, passing the row as $implicit. The template still evaluates against, and by default injects from, the consumer that declared it.

open as a page

With Angular's NgTemplateOutlet, what happens to the rendered view when the context object changes versus when the template or injector input changes?

level: seniorimportance: nice to knowfreq 24%

basics

~20 s

A new template or injector makes NgTemplateOutlet destroy the embedded view and create a new one, losing child state. A new context object keeps the view: a Proxy forwards reads to whatever object is currently bound.

open as a page