ngTemplateOutlet
What is ngTemplateOutlet?
ngTemplateOutlet is a directive that allows you to render a template dynamically in your component. Instead of duplicating HTML, you can define a template once and reuse it multiple times with different data.
Why Use It?
- ✅ Avoid code duplication - Define template once, use many times
- ✅ Dynamic rendering - Switch between different templates at runtime
- ✅ Reusable components - Let parent components customize child templates
- ✅ Conditional layouts - Show different UI based on conditions
- ✅ List customization - Allow custom item rendering in lists
When to Use It?
| Use Case | Example |
|---|---|
| Reusable UI patterns | Cards, list items, modals |
| Conditional layouts | Different views for logged in/out users |
| Customizable components | Tables, lists with custom row templates |
| Template switching | Different layouts based on viewport or user preference |
| Avoiding duplication | Same HTML structure with different data |
Basic Usage
- Basic Example
- How It Works
import { Component } from '@angular/core';
import { NgTemplateOutlet } from '@angular/common';
@Component({
selector: 'app-template-demo',
standalone: true,
imports: [NgTemplateOutlet],
template: `
<h1>Using ngTemplateOutlet</h1>
<!-- Render the template here -->
<ng-container *ngTemplateOutlet="greetingTemplate"></ng-container>
<!-- Define the template -->
<ng-template #greetingTemplate>
<p>Hello from template!</p>
<p>This can be reused anywhere!</p>
</ng-template>
`
})
export class TemplateDemoComponent {}
Step 1: Define a template
<ng-template #greetingTemplate>
<p>Hello from template!</p>
</ng-template>
- Use
<ng-template>to define reusable HTML - Give it a reference name with
#greetingTemplate
Step 2: Render the template
<ng-container *ngTemplateOutlet="greetingTemplate"></ng-container>
- Use
*ngTemplateOutletto render it - Pass the template reference (
greetingTemplate) - The HTML inside
<ng-template>appears at this location
Passing Data (Context)
- With Context
- Context Syntax
@Component({
selector: 'app-album-template',
standalone: true,
imports: [NgTemplateOutlet],
template: `
<!-- Pass data to template using context -->
<ng-container
*ngTemplateOutlet="albumTemplate; context: {
$implicit: album,
index: 0,
isFeatured: true
}">
</ng-container>
<!-- Template receives data with 'let-' variables -->
<ng-template #albumTemplate let-album let-i="index" let-featured="isFeatured">
<div class="album-card">
<p>Album #{{ i }}: {{ album.name }}</p>
<p>Artist: {{ album.artist }}</p>
<span *ngIf="featured" class="badge">⭐ Featured</span>
</div>
</ng-template>
`
})
export class AlbumTemplateComponent {
album = { name: 'Dark Side of the Moon', artist: 'Pink Floyd' };
}
Passing context:
*ngTemplateOutlet="templateRef; context: { key: value }"
Receiving context in template:
<ng-template #templateRef let-variableName="key">
{{ variableName }}
</ng-template>
$implicit (default value):
// Pass: context: { $implicit: album }
// Receive: let-album (no need for ="key")
<ng-template #temp let-album>
<!-- album is automatically bound to $implicit -->
{{ album.name }}
</ng-template>
Reusable Templates
- Reuse Same Template
- With Loop
@Component({
selector: 'app-album-cards',
standalone: true,
imports: [NgTemplateOutlet],
template: `
<!-- Use the same template 3 times with different data -->
<ng-container *ngTemplateOutlet="albumTemplate; context: {
name: 'Dark Side of the Moon',
artist: 'Pink Floyd'
}"></ng-container>
<ng-container *ngTemplateOutlet="albumTemplate; context: {
name: 'Abbey Road',
artist: 'The Beatles'
}"></ng-container>
<ng-container *ngTemplateOutlet="albumTemplate; context: {
name: 'Thriller',
artist: 'Michael Jackson'
}"></ng-container>
<!-- Define template once -->
<ng-template #albumTemplate let-name="name" let-artist="artist">
<div class="album-card">
<h2>{{ name }}</h2>
<p>🎤 {{ artist }}</p>
</div>
</ng-template>
`
})
export class AlbumCardsComponent {}
Result: Three album cards with the same structure but different content, without duplicating HTML.
@Component({
selector: 'app-track-list',
standalone: true,
imports: [NgTemplateOutlet, NgFor],
template: `
<!-- Render template for each track -->
<div *ngFor="let track of tracks; let i = index">
<ng-container *ngTemplateOutlet="trackTemplate; context: {
$implicit: track,
index: i
}"></ng-container>
</div>
<ng-template #trackTemplate let-track let-i="index">
<div class="track">
<span class="number">{{ i + 1 }}</span>
<h3>{{ track.title }}</h3>
<p>⏱️ {{ track.duration }}s</p>
</div>
</ng-template>
`
})
export class TrackListComponent {
tracks = [
{ title: 'Bohemian Rhapsody', duration: 354 },
{ title: 'Stairway to Heaven', duration: 482 },
{ title: 'Hotel California', duration: 391 }
];
}
Conditional Templates
- Switch Between Templates
- Multiple Templates
@Component({
selector: 'app-player-view',
standalone: true,
imports: [NgTemplateOutlet],
template: `
<button (click)="isPlaying = !isPlaying">Toggle Playback</button>
<!-- Show different template based on condition -->
<ng-container *ngTemplateOutlet="
isPlaying ? playingTemplate : pausedTemplate
"></ng-container>
<ng-template #playingTemplate>
<div class="now-playing">
<h2>🎵 Now Playing: {{ trackTitle }}</h2>
<p>Your playlist is active</p>
<button (click)="pause()">⏸️ Pause</button>
</div>
</ng-template>
<ng-template #pausedTemplate>
<div class="paused">
<h2>⏸️ Playback Paused</h2>
<button (click)="play()">▶️ Resume</button>
</div>
</ng-template>
`
})
export class PlayerViewComponent {
isPlaying = false;
trackTitle = 'Bohemian Rhapsody';
play() { this.isPlaying = true; }
pause() { this.isPlaying = false; }
}
@Component({
selector: 'app-album-view-switcher',
standalone: true,
imports: [NgTemplateOutlet],
template: `
<button (click)="viewMode = 'grid'">📊 Grid View</button>
<button (click)="viewMode = 'list'">📃 List View</button>
<button (click)="viewMode = 'covers'">🎨 Cover Art</button>
<!-- Dynamically switch between 3 view modes -->
<ng-container [ngTemplateOutlet]="
viewMode === 'grid' ? gridView :
viewMode === 'list' ? listView :
coversView
"></ng-container>
<ng-template #gridView>
<div class="grid">📊 Album Grid View</div>
</ng-template>
<ng-template #listView>
<div class="list">📃 Album List View</div>
</ng-template>
<ng-template #coversView>
<div class="covers">🎨 Album Cover Art View</div>
</ng-template>
`
})
export class AlbumViewSwitcherComponent {
viewMode: 'grid' | 'list' | 'covers' = 'grid';
}
Customizable Components
- Reusable List Component
- Parent Using Custom Template
import { Component, input, TemplateRef } from '@angular/core';
import { NgTemplateOutlet, NgFor } from '@angular/common';
@Component({
selector: 'app-list',
standalone: true,
imports: [NgTemplateOutlet, NgFor],
template: `
<div class="list-container">
<div *ngFor="let item of items(); let i = index" class="list-item">
<!-- Render custom template for each item -->
<ng-container *ngTemplateOutlet="itemTemplate(); context: {
$implicit: item,
index: i
}"></ng-container>
</div>
</div>
`
})
export class ListComponent {
// Signal-based inputs
items = input<any[]>([]);
itemTemplate = input.required<TemplateRef<any>>();
}
@Component({
selector: 'app-music-library',
standalone: true,
imports: [ListComponent],
template: `
<!-- Reuse list component with custom album template -->
<app-list [items]="albums" [itemTemplate]="albumItem"></app-list>
<ng-template #albumItem let-album let-i="index">
<div class="album">
<img [src]="album.cover" alt="album cover">
<h3>{{ i + 1 }}. {{ album.name }}</h3>
<p>{{ album.artist }}</p>
</div>
</ng-template>
<hr>
<!-- Same list component with different track template -->
<app-list [items]="tracks" [itemTemplate]="trackItem"></app-list>
<ng-template #trackItem let-track let-i="index">
<div class="track">
<span>#{{ i + 1 }}</span>
<strong>{{ track.title }}</strong>
<span>⏱️ {{ track.duration }}s</span>
</div>
</ng-template>
`
})
export class MusicLibraryComponent {
albums = [
{ name: 'Dark Side of the Moon', artist: 'Pink Floyd', cover: '/album1.png' },
{ name: 'Abbey Road', artist: 'The Beatles', cover: '/album2.png' }
];
tracks = [
{ title: 'Bohemian Rhapsody', duration: 354 },
{ title: 'Stairway to Heaven', duration: 482 }
];
}
Why this is powerful: The ListComponent is reusable for ANY type of data. The parent decides how each item looks!
Real-World Use Case: Data Table
- Reusable Table
- Using the Table
@Component({
selector: 'app-data-table',
standalone: true,
imports: [NgTemplateOutlet, NgFor],
template: `
<table>
<thead>
<tr>
<th *ngFor="let column of columns()">{{ column.label }}</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let row of data()">
<td *ngFor="let column of columns()">
<!-- Render custom cell template -->
<ng-container *ngTemplateOutlet="
column.cellTemplate;
context: { $implicit: row, column: column }
"></ng-container>
</td>
</tr>
</tbody>
</table>
`
})
export class DataTableComponent {
// Signal-based inputs
columns = input<Column[]>([]);
data = input<any[]>([]);
}
interface Column {
label: string;
cellTemplate: TemplateRef<any>;
}
import { Component, viewChild, computed, TemplateRef } from '@angular/core';
@Component({
selector: 'app-albums',
standalone: true,
imports: [DataTableComponent],
template: `
<app-data-table [columns]="columns()" [data]="albums"></app-data-table>
<!-- Custom template for title column -->
<ng-template #titleCell let-album>
<strong>{{ album.name }}</strong>
</ng-template>
<!-- Custom template for artist column -->
<ng-template #artistCell let-album>
<a href="/artist/{{ album.artist }}">{{ album.artist }}</a>
</ng-template>
<!-- Custom template for status column -->
<ng-template #statusCell let-album>
<span [class]="album.featured ? 'featured' : 'standard'">
{{ album.featured ? '⭐ Featured' : 'Standard' }}
</span>
</ng-template>
`
})
export class AlbumsComponent {
// Signal-based queries - no lifecycle hook needed!
titleCell = viewChild.required<TemplateRef<any>>('titleCell');
artistCell = viewChild.required<TemplateRef<any>>('artistCell');
statusCell = viewChild.required<TemplateRef<any>>('statusCell');
albums = [
{ name: 'Dark Side of the Moon', artist: 'Pink Floyd', featured: true },
{ name: 'Abbey Road', artist: 'The Beatles', featured: false }
];
// Computed signal for columns
columns = computed(() => [
{ label: 'Title', cellTemplate: this.titleCell() },
{ label: 'Artist', cellTemplate: this.artistCell() },
{ label: 'Status', cellTemplate: this.statusCell() }
]);
}
Best Practices
- ✅ Use for template reusability - Avoid duplicating HTML
- ✅ Great for customizable components - Let consumers define how items look
- ✅ Prefer over complex
*ngIfchains - Cleaner than nested conditionals - ✅ Use
$implicitfor primary context value - Simpler syntax - ✅ Combine with
contentChild()for advanced patterns - More flexible APIs - ❌ Don't overuse - Simple components don't need templates
- ❌ Avoid deep nesting - Keep templates shallow and readable
Common Patterns
| Pattern | Use Case |
|---|---|
| Single template, multiple renders | Cards, alerts, modals |
| Template switching | Different layouts based on state |
| Parent-provided templates | Customizable lists, tables, grids |
| Conditional templates | Logged-in/out views, admin/user UI |
| Template with loops | Dynamic list rendering |
ngTemplateOutlet vs Alternatives
| Approach | When to Use |
|---|---|
| ngTemplateOutlet | Need to reuse or switch templates dynamically |
| ngIf/Else | Simple show/hide logic (2 options max) |
| Component | Complex logic, lifecycle hooks needed |
| ngSwitch | Multiple conditions based on single value |