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.

TypeScript custom-card.component.ts
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:

HTML app.component.html
<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.

TypeScript complex-card.component.ts
@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:

HTML app.component.html
<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.

TypeScript fallback.ts
@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.

HTML ngProjectAs.html
<!-- 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.

Weiterführende Ressourcen

Externe Quellen

/ Weiter

Zurück zu Queries & Projection

Zur Übersicht