ngTemplateOutlet
What is ngTemplateOutlet?
Section titled “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?
Section titled “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?
Section titled “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
Section titled “Basic Usage”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)
Section titled “Passing Data (Context)”@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
Section titled “Reusable Templates”@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
Section titled “Conditional 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
Section titled “Customizable Components”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
Section titled “Real-World Use Case: Data 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
Section titled “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
Section titled “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
Section titled “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 |

