Maîtriser ng-content pour créer des composants flexibles avec projection de contenu, slots nommés et transclusion avancée.
Qu'est-ce que ng-content ?
ng-content est un mécanisme Angular de projection de contenu (aussi appelée transclusion). Il permet à un composant parent de passer du contenu au composant enfant sans passer par les @Input classiques.
Avantages:
- Composants décorés plus flexibles et composables.
- Séparation entre la structure (composant parent) et le contenu (enfant).
- Pas besoin de parser des strings ou manipuler le DOM manuellement.
- Support natif de toutes les directives Angular du parent.
Projection simple de contenu
La forme la plus basique: le composant enfant place <ng-content> là où doit s'afficher le contenu du parent.
Composant Card (enfant):
import { Component } from '@angular/core';
@Component({
selector: 'app-card',
template: `
<div class="card">
<div class="card-header">
<h3>Titre</h3>
</div>
<div class="card-body">
<ng-content></ng-content>
</div>
</div>
`,
styleUrls: ['./card.component.css']
})
export class CardComponent {}
Utilisation (parent):
<app-card>
<p>Ceci est mon contenu personnalisé</p>
<button>Cliquez-moi</button>
</app-card>
Le paragraphe et le bouton s'affichent dans le <ng-content> du composant Card.
Slots nommés avec select
Pour projeter du contenu à plusieurs endroits, utilise l'attribut select. C'est puissant pour les layouts complexes.
Composant Modal (enfant):
@Component({
selector: 'app-modal',
template: `
<div class="modal-overlay">
<div class="modal-box">
<div class="modal-header">
<ng-content select=".modal-title"></ng-content>
</div>
<div class="modal-body">
<ng-content></ng-content>
</div>
<div class="modal-footer">
<ng-content select="[modal-action]"></ng-content>
</div>
</div>
</div>
`
})
export class ModalComponent {}
Utilisation:
<app-modal>
<h2 class="modal-title">Confirmation</h2>
<p>Êtes-vous sûr ?</p>
<button modal-action (click)="onConfirm()">OK</button>
<button modal-action (click)="onCancel()">Annuler</button>
</app-modal>
Sélecteurs disponibles
select=".class-name"— sélectionne par classe CSS.select="tag-name"— sélectionne par nom de balise.select="[attribute-name]"— sélectionne par attribut.- Pas de
select— capture tout le contenu non-sélectionné.
Contenu par défaut et fallback
Si le parent n'envoie pas de contenu pour un slot, tu peux afficher un contenu par défaut.
@Component({
selector: 'app-panel',
template: `
<div class="panel">
<ng-content select=".panel-icon">
<span class="default-icon">ℹ️</span>
</ng-content>
<div class="panel-content">
<ng-content>
<p>Pas de contenu fourni</p>
</ng-content>
</div>
</div>
`
})
export class PanelComponent {}
Patterns avancés
1. Accéder aux éléments projetés avec @ContentChild
import { Component, ContentChild, ElementRef } from '@angular/core';
@Component({
selector: 'app-form-group',
template: `<div class="form-group"><ng-content></ng-content></div>`
})
export class FormGroupComponent {
@ContentChild('input') inputRef?: ElementRef;
ngAfterContentInit() {
if (this.inputRef?.nativeElement) {
this.inputRef.nativeElement.classList.add('form-control');
}
}
}
2. ng-content avec *ngIf
@Component({
template: `
<div class="alert">
<ng-content select=".alert-icon" *ngIf="hasIcon"></ng-content>
<ng-content></ng-content>
</div>
`
})
export class AlertComponent {
@Input() hasIcon = true;
}
Cas d'usage pratiques
- Cards flexibles — titre, contenu, footer personnalisés.
- Modales — header, body, footer avec projections séparées.
- Layouts complexes — sidebar + main + footer = ng-content multiples.
- Composed components — wrappers de formulaires, tables custom, etc.
- Rendering conditional — afficher/cacher du contenu selon des conditions.
Bonnes pratiques et performances
- Garder
ng-contentsimple → utiliseselectplutôt que des logiques complexes. - Ne pas mettre trop de
ng-contentdans un composant (max 3-4 slots). - Utiliser des commentaires HTML pour documenter les slots disponibles.
ng-contentn'a pas d'impact performance → les éléments projetés ne sont pas re-rendus inutilement.- Préférer les slots nommés pour l'évolutivité plutôt qu'un
ng-contentgénérique.