ng-template Angular : ngTemplateOutlet, TemplateRef, ViewContainerRef, contexte type-safe, contentchild, control flow Angular 17 et patterns design system.
ng-template : rôle et fonctionnement
<ng-template> est une balise Angular qui définit un bloc de contenu non rendu par défaut. Elle sert de conteneur pour du contenu conditionnel, réutilisable ou créé dynamiquement.
ng-template n'est jamais présent dans le DOM final. Il ne génère aucun élément HTML — c'est un blueprint que Angular utilise pour créer du contenu à la demande.
Comprendre la désucration des directives structurelles :
<!-- Syntaxe sucrée (shorthand) -->
<div *ngIf="condition">Contenu</div>
<!-- Désucré par Angular en interne -->
<ng-template [ngIf]="condition">
<div>Contenu</div>
</ng-template>
<!-- Idem pour *ngFor -->
<li *ngFor="let item of items">{{ item }}</li>
<!-- Désucré en -->
<ng-template ngFor let-item [ngForOf]="items">
<li>{{ item }}</li>
</ng-template>
| Élément | Dans le DOM | Usage |
|---|---|---|
ng-template | Jamais | Blueprint conditionnel/dynamique |
ng-container | Jamais | Grouper sans créer d'élément |
ng-content | Son contenu projeté | Content projection (slots) |
div (avec *ngIf) | Oui si vrai | Élément conditionnel avec wrapper |
Nouvelle syntaxe @if / @for (Angular 17+)
Angular 17 introduit une nouvelle syntaxe de flow control avec @if, @for et @switch. Plus lisible et plus performante que les directives *ngIf et *ngFor.
<!-- @if avec @else if et @else -->
@if (user.role === 'admin') {
<admin-dashboard />
} @else if (user.role === 'editor') {
<editor-panel />
} @else {
<viewer-mode />
}
<!-- @for avec variable implicite $index, $first, $last, $even, $odd -->
@for (item of items; track item.id) {
<div [class.first]="$first" [class.last]="$last">
{{ $index + 1 }}. {{ item.name }}
</div>
} @empty {
<p>Aucun élément à afficher.</p>
}
<!-- @switch / @case / @default -->
@switch (status) {
@case ('loading') { <mat-spinner /> }
@case ('error') { <error-state /> }
@case ('empty') { <empty-state /> }
@default { <data-table [data]="data" /> }
}
@for : il remplace trackBy et est requis pour les performances. Utilise track item.id ou track $index si pas d'identifiant unique.
Avantages vs directives *ngIf / *ngFor
- Pas besoin d'importer
NgIf,NgFor,NgSwitchdans le composant @emptydans@forremplace le pattern*ngIf="items.length === 0"- Compilation plus rapide (Ivy optimisé)
$index,$first,$lastdisponibles sanslet-index="index"- Lisibilité proche des langages de templating modernes (Svelte, Vue 3)
ng-template avec *ngIf et then/else
La forme classique avec *ngIf — toujours utile pour les templates nommés réutilisables et les patterns then/else explicites.
<!-- Syntaxe then/else avec templates nommés -->
<div *ngIf="isLoggedIn; then loggedInTmpl; else guestTmpl"></div>
<ng-template #loggedInTmpl>
<nav class="user-nav">
<span>Bienvenue, {{ user.name }} !</span>
<button (click)="logout()">Déconnexion</button>
</nav>
</ng-template>
<ng-template #guestTmpl>
<div class="guest-banner">
<p>Vous n'êtes pas connecté.</p>
<button mat-flat-button (click)="openLogin()">Connexion</button>
</div>
</ng-template>
Pattern utile pour les états de chargement avec async pipe :
<!-- Gestion loading/error/data avec ng-template -->
<ng-container *ngIf="users$ | async as users; else loadingTmpl">
<user-list [users]="users" />
</ng-container>
<ng-template #loadingTmpl>
<div class="d-flex justify-content-center p-4">
<mat-spinner diameter="40"></mat-spinner>
</div>
</ng-template>
ng-container vs div avec *ngIf
Préfère ng-container comme wrapper d'ancrage — il ne crée aucun élément DOM, évitant les divs parasites qui cassent les layouts Flexbox/Grid.
Variables de template avec let
Le mot-clé let crée des variables locales dans le contexte d'un template. Indispensable pour éviter de répéter des expressions coûteuses.
<!-- let avec *ngIf + async — évite 3 souscriptions -->
<ng-container *ngIf="{
user: user$ | async,
config: config$ | async,
permissions: permissions$ | async
} as vm">
@if (vm.user && vm.config) {
<app-dashboard
[user]="vm.user"
[config]="vm.config"
[permissions]="vm.permissions"
/>
}
</ng-container>
<!-- let dans un ngFor étendu -->
<ng-template ngFor let-product [ngForOf]="products" let-i="index" let-last="last">
<product-card
[product]="product"
[position]="i + 1"
[showDivider]="!last"
/>
</ng-template>
ViewModel pattern avec combineLatest
// composant.ts — ViewModel unique pour éviter les async pipes multiples
import { Component, inject } from '@angular/core';
import { combineLatest, map } from 'rxjs';
import { AsyncPipe, NgIf } from '@angular/common';
import { UserService } from './user.service';
import { ConfigService } from './config.service';
interface ViewModel {
user: User;
config: AppConfig;
isAdmin: boolean;
}
@Component({
standalone: true,
imports: [AsyncPipe, NgIf],
template: `
<ng-container *ngIf="vm$ | async as vm">
<!-- vm est toujours défini ici — plus de ?. partout -->
<h1>{{ vm.user.name }}</h1>
@if (vm.isAdmin) { <admin-panel /> }
</ng-container>
`
})
export class DashboardComponent {
private userSvc = inject(UserService);
private configSvc = inject(ConfigService);
// Un seul Observable → une seule souscription async pipe
vm$ = combineLatest([this.userSvc.user$, this.configSvc.config$]).pipe(
map(([user, config]): ViewModel => ({
user,
config,
isAdmin: user.roles.includes('admin'),
}))
);
}
ng-template avec *ngFor
La syntaxe desugared de *ngFor donne accès à toutes les variables de contexte et permet des patterns avancés comme les colonnes alternées.
<!-- Syntaxe complète ngFor desugared -->
<ng-template
ngFor
let-item
[ngForOf]="products"
[ngForTrackBy]="trackById"
let-index="index"
let-count="count"
let-first="first"
let-last="last"
let-even="even"
let-odd="odd"
>
<div
class="product-item"
[class.first-item]="first"
[class.last-item]="last"
[class.alt-row]="odd"
>
<span class="badge">{{ index + 1 }}/{{ count }}</span>
<product-card [product]="item" />
</div>
</ng-template>
// Toujours fournir trackBy pour les performances
trackById = (index: number, item: Product) => item.id;
// Sans trackBy : Angular redétruit TOUS les composants à chaque changement
// Avec trackBy : seuls les éléments modifiés/ajoutés/supprimés sont recréés
ngTemplateOutlet — affichage dynamique
ngTemplateOutlet permet d'afficher un template à un endroit précis avec un contexte personnalisé. Idéal pour les composants configurables depuis l'extérieur.
<!-- Afficher un template avec contexte -->
<ng-container
[ngTemplateOutlet]="customHeader || defaultHeader"
[ngTemplateOutletContext]="{ $implicit: item, index: i }"
></ng-container>
<!-- Template par défaut (fallback) -->
<ng-template #defaultHeader let-item let-i="index">
<h3>{{ i + 1 }}. {{ item.title }}</h3>
</ng-template>
<!-- Template personnalisé passé depuis le parent -->
<app-data-list [items]="products" [headerTemplate]="myCustomHeader"></app-data-list>
<ng-template #myCustomHeader let-product let-i="index">
<div class="custom-header">
<img [src]="product.thumbnail" />
<h3>{{ product.name }}</h3>
</div>
</ng-template>
Composant data-list avec template configurable
// data-list.component.ts — ngTemplateOutlet pattern
import { Component, input } from '@angular/core';
import { NgTemplateOutlet } from '@angular/common';
@Component({
selector: 'app-data-list',
standalone: true,
imports: [NgTemplateOutlet],
template: `
@for (item of items(); track item.id; let i = $index) {
<div class="list-item">
@if (headerTemplate()) {
<!-- Template custom passé depuis le parent -->
<ng-container
[ngTemplateOutlet]="headerTemplate()!"
[ngTemplateOutletContext]="{ $implicit: item, index: i }"
/>
} @else {
<!-- Rendu par défaut -->
<h3>{{ item.title }}</h3>
}
<ng-content />
</div>
}
`
})
export class DataListComponent<T extends { id: string | number; title: string }> {
items = input.required<T[]>();
headerTemplate = input<TemplateRef<any> | null>(null);
}
TemplateRef et ViewContainerRef
TemplateRef représente une référence vers un ng-template. ViewContainerRef permet de créer et insérer des vues dans le DOM de façon programmatique.
// dynamic-modal.directive.ts — Directive qui ouvre un ng-template en modal
import {
Directive, Input, HostListener,
TemplateRef, ViewContainerRef, EmbeddedViewRef
} from '@angular/core';
@Directive({
selector: '[appModal]',
standalone: true
})
export class ModalDirective {
@Input('appModal') template!: TemplateRef<any>;
@Input('appModalContext') context: Record<string, any> = {};
private viewRef: EmbeddedViewRef<any> | null = null;
constructor(private vcRef: ViewContainerRef) {}
@HostListener('click')
open(): void {
if (this.viewRef) return; // déjà ouvert
// Créer la vue depuis le template avec contexte
this.viewRef = this.vcRef.createEmbeddedView(this.template, {
$implicit: this.context,
close: () => this.close() // passer la méthode close au template
});
}
close(): void {
this.viewRef?.destroy();
this.viewRef = null;
}
}
// Utilisation dans le template parent
// <button [appModal]="confirmTmpl" [appModalContext]="{ item: selectedItem }">
// Supprimer
// </button>
//
// <ng-template #confirmTmpl let-ctx let-close="close">
// <div class="modal-overlay">
// <p>Supprimer {{ ctx.item.name }} ?</p>
// <button (click)="delete(ctx.item); close()">Confirmer</button>
// <button (click)="close()">Annuler</button>
// </div>
// </ng-template>
@ContentChild avec TemplateRef
Combiner @ContentChild avec TemplateRef est le pattern fondamental pour créer des composants génériques et hautement configurables — tables, listes, accordéons.
// table.component.ts — Table générique avec templates custom
import { Component, ContentChild, Input, TemplateRef } from '@angular/core';
import { NgTemplateOutlet } from '@angular/common';
@Component({
selector: 'app-table',
standalone: true,
imports: [NgTemplateOutlet],
template: `
<table class="table table-hover">
<thead>
<tr>
@if (headerTemplate) {
<ng-container [ngTemplateOutlet]="headerTemplate" />
} @else {
@for (col of columns; track col.key) {
<th>{{ col.label }}</th>
}
}
</tr>
</thead>
<tbody>
@for (row of data; track row.id) {
<tr>
@if (rowTemplate) {
<!-- Row complètement custom -->
<ng-container
[ngTemplateOutlet]="rowTemplate"
[ngTemplateOutletContext]="{ $implicit: row }"
/>
} @else {
@for (col of columns; track col.key) {
<td>{{ row[col.key] }}</td>
}
}
</tr>
}
</tbody>
</table>
`
})
export class TableComponent {
@Input() data: any[] = [];
@Input() columns: { key: string; label: string }[] = [];
// Récupère les ng-template depuis le contenu projeté
@ContentChild('header') headerTemplate?: TemplateRef<any>;
@ContentChild('row') rowTemplate?: TemplateRef<any>;
}
// Utilisation avec templates custom
// <app-table [data]="users" [columns]="userCols">
//
// <ng-template #header>
// <th>Avatar</th>
// <th>Nom complet</th>
// <th>Actions</th>
// </ng-template>
//
// <ng-template #row let-user>
// <td><img [src]="user.avatar" class="avatar"></td>
// <td>{{ user.firstName }} {{ user.lastName }}</td>
// <td>
// <button (click)="edit(user)">Modifier</button>
// <button (click)="delete(user)">Supprimer</button>
// </td>
// </ng-template>
//
// </app-table>
Bonnes pratiques et performance
- Angular 17+ : préfère
@if/@forpour le nouveau code — plus rapide et plus lisible - trackBy obligatoire avec
*ngForettrackobligatoire avec@for— évite la re-création complète de la liste - Utilise
ng-containercomme wrapper d'ancrage *ngIf/*ngFor — pas de div parasite dans le DOM - Nomme tes templates avec des suffixes clairs :
#loadingTmpl,#errorTmpl,#emptyTmpl - Pattern ViewModel avec
combineLatest+ un seulasyncpipe — évite les souscriptions multiples ngTemplateOutletavec fallback|| defaultTmplpour les composants configurables- Pour les tables/listes génériques :
@ContentChild+TemplateRefpour les customisations de rows
// Anti-pattern : async pipe répété → 3 souscriptions HTTP
<div *ngIf="user$ | async as user">
<img [src]="(user$ | async)?.avatar"> <!-- 2ème souscription ! -->
<span>{{ (user$ | async)?.name }}</span> <!-- 3ème souscription ! -->
</div>
<!-- Bonne pratique : ViewModel unique -->
<ng-container *ngIf="user$ | async as user"> <!-- 1 seule souscription -->
<img [src]="user.avatar">
<span>{{ user.name }}</span>
</ng-container>
<!-- Encore mieux en Angular 17+ : @if avec async -->
@if (user$ | async; as user) {
<img [src]="user.avatar">
<span>{{ user.name }}</span>
}
ng generate @angular/core:control-flow. Il convertit tous les *ngIf, *ngFor et ngSwitch en @if, @for et @switch dans tout le projet.
ngTemplateOutlet en profondeur — passage de contexte typé
ngTemplateOutlet est l'outil qui transforme ng-template en
composant réutilisable et configurable. Sa syntaxe complète accepte un contexte qui
expose des variables locales accessibles dans le template via la syntaxe let-x.
C'est le mécanisme qui permet aux composants de design system Angular (Material, CDK,
PrimeNG) d'offrir des slots typés et flexibles.
Variables de contexte vs $implicit
Le contexte passé à ngTemplateOutlet est un objet dont chaque clé devient
une variable accessible via let-x="cleDeLObjet". La clé spéciale
$implicit est récupérée sans nom de clé : let-item reçoit
automatiquement $implicit. C'est la convention pour la donnée
principale du template, équivalent de la variable d'itération dans *ngFor.
// Contexte avec $implicit + clés nommées
<ng-container *ngTemplateOutlet="tpl;
context: { $implicit: user, index: i, isLast: i === total - 1 }">
</ng-container>
// Le template extrait avec let-X (= $implicit) et let-x="clé"
<ng-template #tpl let-user let-index="index" let-isLast="isLast">
<div [class.last]="isLast">
{{ index + 1 }}. {{ user.name }}
</div>
</ng-template>
Réutilisation du même template à plusieurs endroits
Un même TemplateRef peut être instancié plusieurs fois avec des contextes différents — utile pour les listes paginées affichées en haut et bas, les modales et toasts qui partagent un layout, ou les tooltips multiples sur une page.
<ng-template #errorBadge let-message>
<span class="badge bg-danger">{{ message }}</span>
</ng-template>
@for (error of validationErrors; track error.field) {
<ng-container *ngTemplateOutlet="errorBadge; context: { $implicit: error.message }"></ng-container>
}
<footer>
<ng-container *ngTemplateOutlet="errorBadge; context: { $implicit: globalError }"></ng-container>
</footer>
<!-- Définition du template avec deux variables de contexte -->
<ng-template #userCard let-user let-isAdmin="isAdmin">
<article class="card" [class.admin]="isAdmin">
<h3>{{ user.name }}</h3>
<p>{{ user.email }}</p>
@if (isAdmin) { <span class="badge">Admin</span> }
</article>
</ng-template>
<!-- Instanciation avec contexte explicite -->
<ng-container
*ngTemplateOutlet="userCard; context: { $implicit: currentUser, isAdmin: true }">
</ng-container>
Sans typage explicite, les variables let-x sont de type any
en mode strict TypeScript — vous perdez tous les bénéfices de l'autocomplete et de
la vérification de types. La méthode statique ngTemplateContextGuard
résout ce problème en informant le compilateur TypeScript du type exact du contexte
passé. C'est le pattern utilisé en interne par NgForOf qui type
automatiquement let-item selon le tableau passé en input. Pour vos
propres directives template, l'investissement de cinq lignes supplémentaires se
rentabilise immédiatement en confort de développement.
Typage TypeScript du contexte (Angular 14+)
// Avec une directive utilitaire — préserve les types dans le template
@Directive({
selector: 'ng-template[appTypedTemplate]',
standalone: true,
})
export class TypedTemplateDirective<T> {
@Input('appTypedTemplate') value!: T;
static ngTemplateContextGuard<T>(
dir: TypedTemplateDirective<T>,
ctx: unknown,
): ctx is { $implicit: T; isAdmin: boolean } {
return true;
}
}
// Usage — autocomplete TypeScript sur let-user
<ng-template
[appTypedTemplate]="currentUser"
let-user
let-isAdmin="isAdmin">
{{ user.name }} <!-- typé User automatiquement -->
</ng-template>
Le pattern ngTemplateContextGuard fonctionne aussi avec les
composants qui consomment des templates via @ContentChild. Si votre
composant app-list reçoit un template de ligne depuis le parent et que
vous voulez que les bindings dans ce template soient correctement typés, exposez
le type attendu via une directive utilitaire. Sans cette précaution, vos
consommateurs perdront le bénéfice du typage strict et leur code applicatif se
remplira de $any() ou de as User pour contourner le
problème.
Pattern : composant Table avec template de cellule custom
// table.component.ts
@Component({
selector: 'app-table',
standalone: true,
imports: [NgTemplateOutlet],
template: `
<table>
<tbody>
@for (row of rows; track row.id) {
<tr>
<td>
<ng-container
*ngTemplateOutlet="cellTemplate || defaultTpl;
context: { $implicit: row }">
</ng-container>
</td>
</tr>
}
</tbody>
</table>
<ng-template #defaultTpl let-row>{{ row.label }}</ng-template>
`,
})
export class TableComponent {
@Input() rows: { id: string; label: string }[] = [];
@ContentChild('cellTpl') cellTemplate?: TemplateRef<unknown>;
}
// Consommation — l'hôte fournit son propre template de cellule
<app-table [rows]="items">
<ng-template #cellTpl let-row>
<strong>{{ row.label }}</strong>
<small>(id: {{ row.id }})</small>
</ng-template>
</app-table>
Rendu programmatique avec ViewContainerRef
Au-delà des directives template, vous pouvez instancier un ng-template
programmatiquement via ViewContainerRef.createEmbeddedView().
C'est utile pour le rendu conditionnel complexe (state machines, wizards), le
contenu dynamique injecté depuis un service, ou les cas où la position du template
doit être calculée à l'exécution.
La différence pratique avec le déclaratif est subtile mais importante. Avec
@if ou *ngIf, Angular crée et détruit la vue en suivant
le cycle de change detection — vous n'avez aucun contrôle direct, juste une
expression booléenne. Avec ViewContainerRef, c'est vous qui
décidez quand instancier la vue, où la placer dans le DOM, quelles données lui
fournir, et quand la détruire. Le contrôle est total, mais vous portez aussi la
responsabilité du cleanup et de la cohérence de l'état.
@Component({
selector: 'app-stage',
template: `
<ng-template #step1 let-data>
<h2>Bienvenue {{ data.user }}</h2>
<button (click)="next()">Suivant</button>
</ng-template>
<ng-template #step2 let-data>
<h2>Choisis ton plan</h2>
<button (click)="next()">Continuer</button>
</ng-template>
<div #host></div>
`,
})
export class StageComponent implements AfterViewInit {
@ViewChild('step1') step1!: TemplateRef<unknown>;
@ViewChild('step2') step2!: TemplateRef<unknown>;
@ViewChild('host', { read: ViewContainerRef }) host!: ViewContainerRef;
private step = 1;
ngAfterViewInit() {
this.render();
}
next() {
this.step++;
this.render();
}
private render() {
this.host.clear();
const tpl = this.step === 1 ? this.step1 : this.step2;
this.host.createEmbeddedView(tpl, { $implicit: { user: 'Alice' } });
}
}
Le bloc render() ci-dessus illustre la mécanique fondamentale : on
clear l'hôte (this.host.clear()) pour détruire la vue précédente, puis
on crée une nouvelle vue depuis le template choisi avec son propre contexte. Sans
le clear préalable, les vues s'accumuleraient dans le DOM — chaque clic sur
« Suivant » ajouterait une étape de plus à l'écran au lieu de remplacer
l'existante. C'est l'erreur classique du rendu programmatique débutant.
Quand utiliser le rendu programmatique vs déclaratif
- Déclaratif (
@if,@for,ngTemplateOutlet) : 95 % des cas. Plus lisible, plus simple à maintenir, change detection optimisée. - Programmatique (
ViewContainerRef) : portails, services qui injectent du contenu (notifications, modals globaux), libs UI bas niveau (CDK Overlay).
Concrètement, vous utiliserez le rendu programmatique dans deux scénarios très
spécifiques. Premièrement, un service de notifications globales
qui doit pouvoir injecter un template dans n'importe quel composant racine, depuis
n'importe où dans le code. Le service récupère un ViewContainerRef
via ApplicationRef et instancie le template à la volée. Deuxièmement,
un composant de wizard ou state machine où la séquence d'étapes
est calculée à l'exécution selon les choix de l'utilisateur — l'enchaînement
déclaratif via @switch devient trop verbeux quand on a 10+ étapes
conditionnelles. Pour tout le reste, restez en déclaratif.
Les composants du CDK Angular (Portal,
Overlay, Dialog) sont entièrement construits sur ce pattern
programmatique. Lire leur code source est un excellent moyen d'apprendre les
subtilités de ViewContainerRef et TemplatePortal en
production réelle. C'est aussi ce qui rend Angular Material si puissant pour
construire des UX avancées sans réinventer la roue.
Conclusion
ng-template est la primitive la plus polyvalente du système de templates
Angular. Elle alimente quatre patterns qu'on retrouve dans toute application
professionnelle : les directives structurelles (*ngIf,
*ngFor), les slots configurables (ngTemplateOutlet
avec @ContentChild), le rendu programmatique
(ViewContainerRef), et les templates de fallback
(@if … @else avec template référencé).
Avec le nouveau control flow d'Angular 17+ (@if, @for,
@switch), beaucoup d'usages basiques de ng-template
deviennent obsolètes — c'est tant mieux, la nouvelle syntaxe est plus lisible. Mais
ngTemplateOutlet, ViewContainerRef et les templates passés
en @ContentChild restent indispensables pour construire des composants
de design system réutilisables et configurables. Comprendre ces patterns vous fait
passer de consommateur à concepteur de composants Angular.
Différences clés avec @ContentChild et @ContentChildren
@ContentChild(TemplateRef) retourne une référence au premier
ng-template projeté. @ContentChildren(TemplateRef) retourne
une QueryList de toutes les références. La différence cruciale : avec
@ContentChild, le template est résolu une fois après ngAfterContentInit
et reste stable. Avec @ContentChildren, la QueryList émet
un changes Observable quand des templates sont ajoutés/retirés dynamiquement
(via @if ou @for autour des templates projetés).
@Component({
selector: 'app-tabs',
template: `<ng-content></ng-content>
@for (tpl of templates; track $index) {
<div class="tab-content">
<ng-container *ngTemplateOutlet="tpl"></ng-container>
</div>
}`,
})
export class TabsComponent implements AfterContentInit {
@ContentChildren(TemplateRef) templateList!: QueryList<TemplateRef<unknown>>;
templates: TemplateRef<unknown>[] = [];
ngAfterContentInit() {
this.templates = this.templateList.toArray();
// Réagir aux changements dynamiques
this.templateList.changes.subscribe(() => {
this.templates = this.templateList.toArray();
});
}
}
Depuis Angular 17, les signal queries (contentChild(),
contentChildren()) remplacent ces décorateurs. La version signal expose
un signal lecture-seule qui se met à jour automatiquement, sans QueryList
ni Observable explicite.
Signal queries Angular 17+ — contentChild et contentChildren
// Signal queries remplacent @ContentChild dans Angular 17+
import { Component, contentChild, contentChildren, TemplateRef } from '@angular/core';
@Component({
selector: 'app-list',
template: `
@for (item of items; track item.id) {
<ng-container *ngTemplateOutlet="rowTpl(); context: { $implicit: item }"></ng-container>
}
`,
})
export class ListComponent {
rowTpl = contentChild.required<TemplateRef<unknown>>('rowTpl');
// Signal lecture seule — réactif aux changements de template
}
NgTemplateOutletInjector — injection de dépendances scope-locales
Depuis Angular 14.1, ngTemplateOutletInjector permet de passer un
injecteur custom au template instancié. Le template peut alors injecter des
services différents de ceux du contexte parent — utile pour fournir un service
scopé spécifique à un sous-arbre sans créer de composant intermédiaire.
const customInjector = Injector.create({
providers: [{ provide: ThemeService, useValue: darkTheme }],
parent: this.injector,
});
// Dans le template
<ng-container *ngTemplateOutlet="myTemplate;
injector: customInjector;
context: { $implicit: data }"></ng-container>
- Préférer
@if/@for/@switch(Angular 17+) aux*ngIf/*ngForen code applicatif - Garder
ng-template+ngTemplateOutletpour les slots configurables et les libs UI - Typer le contexte des templates via
ngTemplateContextGuard - Utiliser
ViewContainerRefpour les rendus programmatiques (portals, notifications) - Pattern ViewModel avec un seul
asyncpipe pour éviter les souscriptions multiples - Nommer les templates clairement :
#loadingTpl,#errorTpl,#cellTpl - Combiner
@ContentChild+TemplateRefpour les composants de table/liste génériques - Migrer automatiquement avec
ng generate @angular/core:control-flow
Investir du temps pour comprendre ng-template et son écosystème
paie sur le long terme. Sur les six prochains mois de votre carrière Angular, vous
consommerez et créerez des dizaines de composants génériques — chacun bénéficiera
des patterns vus ici. Le control flow Angular 17+ simplifie le code applicatif
quotidien, mais c'est ng-template et ses APIs qui vous permettent de
construire votre propre couche d'abstraction réutilisable.