ng-template, ngTemplateOutlet, and ng-container
ng-template
In structural directives, we used ng-template with *ngIf and an else branch — defining a template reference with #templateReferenceName to render alternate content.
<div *ngIf="user.age >= 13; else noPG13">You can watch PG-13 content</div>
<ng-template #noPG13>
<div>You cannot watch PG-13 content</div>
</ng-template>From this example:
- HTML wrapped in
ng-templateis not rendered immediately. It renders only in specific cases — for example, when*ngIfuses it as anelsetemplate, or when you render it throughngTemplateOutlet. - A template is a reusable fragment. Combining multiple templates can build a complete UI.
In short, ng-template is an Angular element for storing HTML that is never displayed at its definition site — only when explicitly rendered.
When should you use ng-template?
Common cases:
- With structural directives such as
*ngIf - Repeated UI fragments inside one component that are too small to extract into a separate component
- Passing templates into other components to override default markup
Repeating UI without copy-paste
Suppose a component has a counter variable and repeats the same badge UI in several places:
<div class="card">
<div class="card-header">
You have selected
<span class="badge badge-primary">{{ counter }}</span> items.
</div>
<div class="card-body">
There are <span class="badge badge-primary">{{ counter }}</span> items was
selected.
</div>
<div class="card-footer">
You have selected
<span class="badge badge-primary">{{ counter }}</span> items.
</div>
</div>Refactor with ng-template and ngTemplateOutlet:
<div class="card">
<div class="card-header">
You have selected
<ng-container [ngTemplateOutlet]="counterTmpl"></ng-container>.
</div>
<div class="card-body">
There are <ng-container [ngTemplateOutlet]="counterTmpl"></ng-container> was
selected.
</div>
<div class="card-footer">
You have selected
<ng-container [ngTemplateOutlet]="counterTmpl"></ng-container>.
</div>
</div>
<ng-template #counterTmpl>
<span class="badge badge-primary">{{ counter }}</span> items
</ng-template>Benefits:
- Change the counter UI in one place instead of three — fewer typos and missed find-and-replace targets.
- The template fits in a single line at each call site — lighter than extracting a whole component.
Overriding a component's default template
A tab-container component might ship with a default tab header:
@Component({
selector: 'tab-container',
template: `
<ng-template #defaultTabButtonsTmpl>
<div class="default-tab-buttons">...</div>
</ng-template>
<ng-container
*ngTemplateOutlet="headerTemplate || defaultTabButtons"
></ng-container>
... rest of tab container component ...
`,
})
export class TabContainerComponent {
@Input() headerTemplate: TemplateRef<any>; // Custom template provided by parent
}The parent can pass a custom header:
@Component({
selector: 'app-root',
template: `
<ng-template #customTabButtons>
<div class="custom-class">
<button class="tab-button" (click)="login()">
{{loginText}}
</button>
<button class="tab-button" (click)="signUp()">
{{signUpText}}
</button>
</div>
</ng-template>
<tab-container [headerTemplate]="customTabButtons"></tab-container>
`
})ngTemplateOutlet
ngTemplateOutlet renders a template created by ng-template:
*ngTemplateOutlet="templateRef"(the*matters for structural directive form)[ngTemplateOutlet]="templateRef"
Like @Input() on components, templates accept context data via ngTemplateOutletContext.
Reuse button markup with different labels and optional icons:
<button class="btn btn-primary">Click here</button>
<button class="btn btn-danger">
<i class="fa fa-remove"></i>
Delete
</button>Refactored:
<ng-template
#buttonTmpl
let-label="label"
let-className="className"
let-icon="icon"
>
<button [ngClass]="['btn', className ? className : '']">
<i *ngIf="icon" class="fa {{icon}}"></i>
{{ label }}
</button>
</ng-template>
<ng-container
[ngTemplateOutlet]="buttonTmpl"
[ngTemplateOutletContext]="{ label: 'Click here', className: 'btn-primary', icon: null }"
>
</ng-container>
<ng-container
[ngTemplateOutlet]="buttonTmpl"
[ngTemplateOutletContext]="{ label: 'Remove', className: 'btn-danger', icon: 'fa-remove' }"
>
</ng-container>Notes:
- In
ng-template,let-name="name"binds a template variable: the leftnameis used inside the template; the rightnameis the key inngTemplateOutletContext. They can differ. let-namewithout="name"uses the$implicitcontext key:
<ng-template #buttonTmpl let-label let-className="className" let-icon="icon">
<button [ngClass]="['btn', className ? className : '']">
<i *ngIf="icon" class="fa {{icon}}"></i>
{{ label }}
</button>
</ng-template>
<ng-container
[ngTemplateOutlet]="buttonTmpl"
[ngTemplateOutletContext]="{ $implicit: 'Remove', className: 'btn-danger', icon: 'fa-remove' }"
>
</ng-container>- Template variables inside
ng-templateare not type-safe. If you pass auserobject withfirstName,lastName, andage, usinguser.fullNamewon't be caught by the compiler or Angular language service.
ng-container
ng-container is a grouping element that doesn't add an extra DOM node — useful when a wrapper would break CSS selectors like parent > child.
You could write:
<div
[ngTemplateOutlet]="buttonTmpl"
[ngTemplateOutletContext]="{ label: 'Click here', class: 'btn-primary', icon: null }"
></div>But that renders an extra wrapper:
<div>
<button class="btn btn-primary">Click here</button>
</div>ng-container avoids the extra element.
Summary
We've introduced ng-template for storing reusable markup, ngTemplateOutlet for rendering it (with context), and ng-container for grouping without extra DOM nodes.
Further reading:
- https://alligator.io/angular/reusable-components-ngtemplateoutlet/
- https://angular.io/guide/structural-directives#the-ng-template
- Angular render recursive view using *ngFor and ng-template
- https://blog.angular-university.io/angular-ng-template-ng-container-ngtemplateoutlet/
