ngTemplateOutlet
Qu'est-ce que ngTemplateOutlet?
ngTemplateOutlet est une directive qui vous permet de rendre un template dynamiquement dans votre composant. Au lieu de dupliquer du HTML, vous pouvez définir un template une fois et le réutiliser plusieurs fois avec différentes données.
Pourquoi l'Utiliser?
- ✅ Éviter la duplication de code - Définir le template une fois, l'utiliser plusieurs fois
- ✅ Rendu dynamique - Basculer entre différents templates à l'exécution
- ✅ Composants réutilisables - Permettre aux composants parents de personnaliser les templates enfants
- ✅ Layouts conditionnels - Afficher différentes interfaces selon les conditions
- ✅ Personnalisation de listes - Permettre un rendu personnalisé des éléments de liste
Quand l'Utiliser?
| Cas d'Utilisation | Exemple |
|---|---|
| Patterns UI réutilisables | Cartes, éléments de liste, modales |
| Layouts conditionnels | Vues différentes pour utilisateurs connectés/déconnectés |
| Composants personnalisables | Tableaux, listes avec templates de lignes personnalisés |
| Changement de template | Layouts différents selon la vue ou les préférences utilisateur |
| Éviter la duplication | Même structure HTML avec différentes données |
Utilisation Basique
- Exemple Basique
- Comment ça Fonctionne
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 {}
Étape 1: Définir un template
<ng-template #greetingTemplate>
<p>Hello from template!</p>
</ng-template>
- Utiliser
<ng-template>pour définir du HTML réutilisable - Lui donner un nom de référence avec
#greetingTemplate
Étape 2: Rendre le template
<ng-container *ngTemplateOutlet="greetingTemplate"></ng-container>
- Utiliser
*ngTemplateOutletpour le rendre - Passer la référence du template (
greetingTemplate) - Le HTML à l'intérieur de
<ng-template>apparaît à cet emplacement
Passer des Données (Contexte)
- Avec Contexte
- Context Syntax
@Component({
selector: 'app-album-template',
standalone: true,
imports: [NgTemplateOutlet],
template: `
<!-- Passer des données au template via le contexte -->
<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 est automatiquement lié à $implicit -->
{{ album.name }}
</ng-template>
Reusable Templates
- Reuse Same Template
- Avec Boucle
@Component({
selector: 'app-album-cards',
standalone: true,
imports: [NgTemplateOutlet],
template: `
<!-- Utiliser 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 Utilisant un Template Personnalisé
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 cusaum template for each item -->
<ng-container *ngTemplateOutlet="itemTemplate(); context: {
$implicit: item,
index: i
}"></ng-container>
</div>
</div>
`
})
export class ListComponent {
// Basé sur signal inputs
items = input<any[]>([]);
itemTemplate = input.required<TemplateRef<any>>();
}
@Component({
selector: 'app-music-library',
standalone: true,
imports: [ListComponent],
template: `
<!-- Reuse list component with cusaum 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 psontnt 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 cusaum cell template -->
<ng-container *ngTemplateOutlet="
column.cellTemplate;
context: { $implicit: row, column: column }
"></ng-container>
</td>
</tr>
</tbody>
</table>
`
})
export class DataTableComponent {
// Basé sur signal 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>
<!-- Template personnalisé pour la colonne titre -->
<ng-template #titleCell let-album>
<strong>{{ album.name }}</strong>
</ng-template>
<!-- Template personnalisé pour la colonne artiste -->
<ng-template #artistCell let-album>
<a href="/artist/{{ album.artist }}">{{ album.artist }}</a>
</ng-template>
<!-- Template personnalisé pour la colonne statut -->
<ng-template #statusCell let-album>
<span [class]="album.featured ? 'featured' : 'standard'">
{{ album.featured ? '⭐ Featured' : 'Standard' }}
</span>
</ng-template>
`
})
export class AlbumsComponent {
// Basé sur signal 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() }
]);
}
Bonnes Pratiques
- ✅ Utiliser pour la réutilisabilité des templates - Éviter la duplication de HTML
- ✅ Excellent pour les composants personnalisables - Permettre aux consommateurs de définir l'apparence des éléments
- ✅ Préférer aux chaînes
*ngIfcomplexes - Plus propre que les conditionnels imbriqués - ✅ Utiliser
$implicitpour la valeur de contexte principale - Syntaxe plus simple - ✅ Combiner avec
contentChild()pour des patterns avancés - APIs plus flexibles - ❌ Ne pas surutiliser - Les composants simples n'ont pas besoin de templates
- ❌ Éviter l'imbrication profonde - Garder les templates simples et lisibles
Common Patterns
| Pattern | Cas d'Utilisation |
|---|---|
| Un template, plusieurs rendus | Cartes, alertes, modales |
| Changement de template | Layouts différents selon l'état |
| Templates fournis par le parent | Listes, tableaux, grilles personnalisables |
| Templates conditionnels | Vues connecté/déconnecté, UI admin/utilisateur |
| Template avec boucles | Rendu de liste dynamique |
ngTemplateOutlet vs Alternatives
| Approche | Quand l'Utiliser |
|---|---|
| ngTemplateOutlet | Besoin de réutiliser ou changer de templates dynamiquement |
| ngIf/Else | Logique simple d'affichage/masquage (2 options max) |
| Component | Complex logic, lifecycle hooks needed |
| ngSwitch | Multiple conditions based on single value |