In Angular ist die Wiederverwendbarkeit von Komponenten ein zentrales Konzept. Doch was passiert, wenn du dynamischen Inhalt in eine Komponente einfügen möchtest, ohne deren Struktur zu verändern? Genau hier kommt das mächtige Konzept der Content Projection ins Spiel – realisiert durch das <ng-content>-Element.

Mit dieser Technik kannst du flexibel Inhalte aus dem Parent-Komponenten-Kontext in eine Child-Komponente einfügen. In diesem Artikel lernst du, wie ng-content funktioniert, wann du es einsetzen solltest, und wie du damit auch komplexe Strukturen wie mehrere Projektionen oder selektive Platzhalter umsetzt.

Grundlegende Verwendung

Wenn man einen bestimmten Inhalt aus einem anderen Component in das aktuelle Component hineingeben möchte, kann man es sehr einfach mit ng-content tun.

Ohne besondere Konfigurationen kann man dies mit Platzierung von ng-content an der Stelle, an welcher der Inhalt projeziert werden soll, angeben.

TypeScript custom-card.component.ts
import { Component } from '@angular/core';

@Component({
    selector: 'custom-card',
    imports: [],
    template: `
        <div class="custom-card">
            <ng-content></ng-content>
        </div>
    `
})
export class CustomCard {}

Mit dieser Definition kann man nun dieses Component an einer anderen Stelle verwenden und Inhalte hineingeben, die zwischen <div class="custom-card"> und </div> gerendert werden.

TypeScript other.component.ts
import { Component } from '@angular/core';

import { CustomCard } from './custom-card/custom-card.component.ts';

@Component({
    selector: 'custom-card',
    imports: [
        CustomCard
    ],
    template: `
        <custom-card>
            Dieser Inhalt wird in das Component hinein projeziert.
        </custom-card>
    `
})
export class CustomCard {}

Angular Components - Content Projection - Base

Man kann selbstverständlich auch andere Elemente und weitere Components an dieser Stelle platzieren. Im Grunde dient ng-content wie ein Platzhalter.

Im folgenden Beispiel sieht man, wie HTML-Tags und ein anderer Component in das aktuelle Component hineingegeben werden.

Zuerst definieren wir ein Placeholder-Component, das einfach nur einen statischen, formattierten Text ausgibt. Dadurch können wir sehen, dass gerenderte Inhalt aus diesem Component stamm.

TypeScript placeholder.component.ts
import { Component } from '@angular/core';

@Component({
    selector: 'app-placeholder',
    imports: [],
    template: `
        <strong>
            <i>Ich bin vom anderen Component.</i>
        </strong>
    `
})
export class PlaceholderComponent {}

Die Verwendung im Parent-Component oder einem anderen Component, in dem Content Projection zum Einsatz kommen soll, ist wie man es erwarten würde - einfach die Tags und Components verwenden, wie man es sonst tut.

TypeScript other.component.ts
import { Component } from '@angular/core';

import { CustomCard } from './custom-card/custom-card.component.ts';
import { PlaceholderComponent } from './placeholder/placeholder.component.ts';

@Component({
    selector: 'app-placeholder',
    imports: [
        CustomCard,
        PlaceholderComponent
    ],
    template: `
        <custom-card>
            <h4>Eine H4-Überschrift</h4>
            <app-placeholder></app-placeholder>
        </custom-card>
    `
})
export class CustomCard {}

Angular Components - Content Projection - Tags und Components

Angular’s ng-content ist weder ein Component, noch ist es ein DOM-Element. Es ist ein Platzhalter, welcher Angular mitteilt, dass an dieser Stelle etwas ausgegeben (gerendert) werden soll. Alle ng-content Platzhalter werden während der Kompilierung ersetzt.

Wichtiger Hinweis

Man sollte ng-content nicht mit Hilfe von Bedingungslogik und @if, @for oder @switch einbinden. Angular wird immer Elemente für ng-content Platzhalter erstellen, auch wenn ng-content versteckt ist.

Mehrere Slots

Manchmal möchte man nicht den gesamten Content an derselben Stelle platzieren, sondern z.B. einen Header-Teil, einen Body-Teil und einen Footer-Teil haben. Oder anders formuliert - man möchte Inhalte aufteilen.

An diesem Punkt kommen die sogenannten Slots zum Einsatz. Für die Auswahl des jeweiligen Ziel-Slots kommen CSS-Selektoren zum Einsatz.

Im folgenden Beispiel werden wir 2 Slots angeben und diese mit Selektoren ausstatten, sodass bei der Verwendung wir genau festlegen können, wohin welcher Inhalt geht.

Wir brauchen hauptsächlich in unserem Beispiel nur das Template.

TypeScript card-extended.component.html
<div class="custom-card-extended">
    <div class="card-title">
        <h3>
            <ng-content
                select=[card-title-slot]
            ></ng-content>
        </h3>
    </div>
    <div class="card-title">
        <ng-content
            select=[card-body-slot]
        ></ng-content>
    </div>
</div>

Hier wurden Selektoren als Element-Attribute verwendet. Die Verwendung dieses Components wird an einer anderen Stell wie folgt aussehen.

TypeScript parent.component.html
<app-card-extended>
    <div card-title-slot>Hallo</div>
    <div card-body-slot>
        Hier kommt der Body der Card
    </div>
</app-card-extended>

Angular Components - Content Projection - Multiple Slots

Standard Inhalte

Wenn man irgendwo ng-content einsetzt und auch dann eine bestimmte Ausgabe bzw. Inhalt an dieser Stelle gerendert haben möchte, wenn keine Übereinstimmung mit den gewählten Selektoren gibt, kann man Standard-Inhalte platzieren. In anderen Worten: Wenn Angular nichts findet, was hier gerendert werden soll, verwendet es dann diese Standard-Inhalte.

TypeScript custom-card.component.html
<div class="custom-card">
    <ng-content>
        Hier steht ein Standard-Inhalt
    </ng-content>
</div>

Die Verwendung kann dann auf zwei Wegen erfolgen. Man kann eigenen Inhalt übergeben oder, wenn man es nicht tut, es wird ein Standard-Inhalt ausgegeben, welchen man direkt im Component definiert hat.

TypeScript parent.component.html
<app-custom-card></app-custom-card>
<app-custom-card>
    Dieser Inhalt wurde übergeben.
</app-custom-card>

Angular Components - Content Projection - Fallback content

Aliasing mit ngProjectAs

Manmal möchte man Tag- oder Attribut-Namen nicht fest vorgeben, sondern flexibel sein oder HTML-Standardelemente verwenden.

In diesem Fall hilft das sogenannte Aliasing.

TypeScript custom-title.component.html
<ng-content select="custom-title-slot"></ng-content>

Bei der Verwendung wird ngProjectAs eingesetzt.

TypeScript other.component.html
<app-custom-title>
    <h3 ngProjectAs="custom-title-slot">
        Custom title
    </h3>
</app-custom-title>

Beispiel - Tabs

Im folgenden Beispiel wird ein Element Eigene Tabs zur Verdeutlichung von ng-content gezeigt.

Für dieses Beispiel werden 2 Components gebraucht.

  1. Ein Container-Component. Dieses Component stellt den äußeren Container für alle einzelnen Tab-Elemente dar.
  2. Ein Element-Component. Dieses Component repräsentiert einen einzelnen Tab, welcher bestimmten (eigenen Inhalt) aufnehmen kann.
TypeScript tab.component.ts
import { Component, input } from '@angular/core';

@Component({
    selector: 'app-tab',
    imports: [],
    templateUrl: './tab.component.html',
    styleUrl: './tab.component.scss'
})
export class TabComponent {

    title = input<string>('');
    active = false;

}

Unser Tab-Component hat zwei Eigenschaften: title und active. Der Titel ist der Wert, welcher am Button zur Steuerung/Aktivierung des Tabs benötigt wird. Die Kennzeichnung als active wird benötigt, um mitzuteilen, ob dieser Tab aktuell aktiv oder inaktiv ist.

Das Template ist relativ einfach aufgebaut. Der Inhalt wird nur dann ausgegeben, wenn dieser Tab tatsächlich aktiv ist.

TypeScript tab.component.html
@if (active) {
    <div class="tab_pane">
        <ng-content></ng-content>
    </div>
}

Als nächsten benötigen wir ein Container, welcher die einzelnen Tabs in sich aufnehmen kann.

TypeScript tabs.component.ts
import { Component, ContentChildren, QueryList, AfterContentInit } from '@angular/core';
import { CommonModule } from '@angular/common';

import { TabComponent } from './tab/tab.component';

@Component({
    selector: 'app-tabs',
    imports: [
        CommonModule
    ],
    templateUrl: './tabs.component.html',
    styleUrl: './tabs.component.scss'
})
export class TabsComponent implements AfterContentInit {

    @ContentChildren(TabComponent) tabs!: QueryList<TabComponent>

    ngAfterContentInit(): void {
        this.open(this.tabs.first);
    }

    /**
    * Set tab active
    * ---
    * @param tab - Target tab
    */
    open(tab: TabComponent): void {
        this.tabs.forEach(t => t.active = false);
        tab.active = true;
    }

}
TypeScript tabs.component.html
<div class="custom_tabs">
    <div class="tab_triggers">
        @for (tab of tabs; track tab) {
            <button (click)="open(tab)" [ngClass]="{'active': tab.active}">
                {{ tab.title() }}
            </button>
        }
    </div>
    <div class="tab_panes">
        <ng-content></ng-content>
    </div>
</div>

Die Verwendung ist dann relativ einfach. Wir staffeln einfach die einzelnen app-tab Elemente in einem app-tabs Container (Component) und geben die Inhalte an, die über ng-content gerendert werden.

TypeScript other.component.html
<app-tabs>
    <app-tab title="First tab">
        <p>Here is the content of first tab.</p>
    </app-tab>
    <app-tab title="Second tab">
        <p>Here is the content of second tab.</p>
    </app-tab>
    <app-tab title="Third tab">
        <p>Here is the content of third tab.</p>
    </app-tab>
</app-tabs>

Angular Components - Content Projection - Beispiel Tabs (1)

Angular Components - Content Projection - Beispiel Tabs (2)

/ Weiter

Zurück zu Components

Zur Übersicht