Oftmals sollen Komponenten als flexible “Container” oder “Wrapper” agieren. Ein klassisches Beispiel ist eine Card-Komponente: Sie gibt den Rahmen, den Schatten und das grundlegende Layout vor, aber der eigentliche Inhalt (Text, Bilder, Formulare) soll von der Komponente bestimmt werden, die diese Card verwendet. In Angular nennt sich dieses Konzept Content Projection und wird primär über den <ng-content>-Tag abgebildet.
Grundlagen: <ng-content>
Das Element <ng-content> ist kein echtes HTML-Tag und auch keine Angular-Komponente. Es fungiert lediglich als Platzhalter (ähnlich dem nativen HTML <slot>-Element). Angular ersetzt diesen Platzhalter zur Kompilierzeit durch den Inhalt, den die Parent-Komponente übergibt.
import { Component } from '@angular/core';
@Component({
selector: 'app-custom-card',
template: `
<div class="card-shadow">
<!-- Hier wird der externe Inhalt projiziert -->
<ng-content />
</div>
`,
standalone: true
})
export class CustomCardComponent {}Nutzung der Container-Komponente
Wenn du app-custom-card nun in einer anderen Komponente verwendest, kannst du beliebigen HTML-Code zwischen die öffnenden und schließenden Tags schreiben:
<app-custom-card>
<h2>Profil</h2>
<p>Das ist ein projizierter Text.</p>
<button>Speichern</button>
</app-custom-card>Angular fügt diese drei Elemente (h2, p, button) nahtlos in den <ng-content>-Platzhalter ein.
Multiple Content Placeholders (Mehrere Slots)
Ein einzelner Platzhalter reicht oft nicht aus. Stell dir vor, deine Card hat einen separaten Header-Bereich, einen Body und einen Footer. Du kannst mehrere <ng-content> Tags verwenden und diese über das select-Attribut mit CSS-Selektoren filtern.
@Component({
selector: 'app-complex-card',
template: `
<div class="card">
<header>
<!-- Fängt nur Elemente mit dem Attribut "card-title" -->
<ng-content select="[card-title]"></ng-content>
</header>
<main>
<!-- Fängt alles andere (Catch-All) -->
<ng-content></ng-content>
</main>
<footer>
<!-- Fängt nur Elemente mit der Klasse "card-footer" -->
<ng-content select=".card-footer"></ng-content>
</footer>
</div>
`,
standalone: true
})
export class ComplexCardComponent {}Die Parent-Komponente verteilt ihre Inhalte dann gezielt:
<app-complex-card>
<h2 card-title>Willkommen zurück</h2>
<p>Dieser Text landet im Catch-All main-Bereich.</p>
<button class="card-footer">Abbrechen</button>
<button class="card-footer">OK</button>
</app-complex-card>Fallback-Inhalte definieren
Manchmal möchtest du einen Standard-Text (Fallback) anzeigen, falls die Parent-Komponente nichts in den Platzhalter projiziert. Dies erreichst du, indem du einfach Inhalt in das <ng-content>-Element schreibst.
@Component({
selector: 'app-user-profile',
template: `
<div class="profile">
<ng-content select="[user-avatar]">
<!-- Wird nur angezeigt, wenn kein [user-avatar] übergeben wurde -->
<img src="default-avatar.png" alt="Standard Profilbild" />
</ng-content>
</div>
`
})
export class UserProfileComponent {}Das ngProjectAs Attribut
Es gibt Situationen, in denen du die HTML-Struktur der Parent-Komponente nicht verändern möchtest oder kannst (z.B. weil du an semantische Tags gebunden bist), diese Struktur aber nicht zum select-Attribut des <ng-content> passt.
Mit ngProjectAs kannst du Angular zwingen, ein Element für die Content Projection so zu behandeln, als würde es einen bestimmten CSS-Selektor erfüllen, ohne dass du diesen Selektor (z.B. eine Klasse) physisch an das Element hängen musst.
<!-- Die Card erwartet intern ein Element, das auf "card-title" matcht. -->
<!-- Wir nutzen ein Standard <h3>, weisen Angular aber an, es als "card-title" zu projizieren. -->
<app-complex-card>
<h3 ngProjectAs="[card-title]">Mein Titel</h3>
</app-complex-card>Interessantes
Fallback-Content rendert nur ohne Match
Schreibst du Inhalt zwischen <ng-content>…</ng-content>, wird dieser ausschließlich gerendert, wenn nichts Passendes projiziert wurde. Das funktioniert pro Slot – jedes ng-content mit eigenem select hat seinen eigenen Fallback.
select akzeptiert komplette CSS-Selektoren
Du bist nicht auf Tag-Namen oder Attribute beschränkt. Klassen (.card-footer), Attribut-Selektoren mit Werten ([type=“submit”]) oder Kombinationen wie button.primary sind erlaubt – matched wird streng gegen den projizierten Wurzelknoten.
Catch-All ohne select ist optional
Existiert kein leeres <ng-content> in der Komponente, fallen nicht-matchende Elemente einfach weg. Das ist nützlich, um ungewollte Inhalte stillschweigend zu filtern – aber auch eine häufige Quelle für „mein Inhalt verschwindet”-Debugging.
ngProjectAs ist statisch
Das Attribut akzeptiert nur literale Werte – kein Property-Binding wie [ngProjectAs]. Das macht dynamische Slot-Wahl unmöglich; bei Bedarf nimm ngTemplateOutlet mit getrennten TemplateRefs.
Compile-Time, nicht Runtime
<ng-content> ist kein DOM-Knoten, sondern eine Compiler-Anweisung. Deshalb darf es weder in @if/@for stehen noch Direktiven/Styles tragen – die Projektion wird zur Build-Zeit aufgelöst.