navigation Navigation


Content Projection


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.

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.

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.

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.

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.

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.

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.

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.

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.

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

Bei der Verwendung wird ngProjectAs eingesetzt.

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.
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.

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.

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;
    }

}
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.

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)