Angular Content Projection Schritt-für-Schritt

26. August 2024  in Angular by Jan Kruse j1n ‐ 5 min Lesezeit

Erfahre in diesem Artikel, wie du Content Projection in Angular effektiv nutzt. Von den Grundlagen bis zu fortgeschrittenen Techniken – praktische Beispiele, um deine Komponenten flexibel und wartbar zu gestalten.

PC-Bildschirm auf einem Holzschreibtisch mit geöffneter IDE

Hast du dich schon oft gefragt, welche Struktur für Angular-Komponenten in deinem Projekt am sinnvollsten ist?
Oder warum der Datenfluss mit der Zeit immer unübersichtlicher wird?
Vielleicht fragst du dich, ob ein @Input() für eine bestimmte Komponente wirklich notwendig ist – oder welche Alternativen es gibt?

Dann solltest du einen genaueren Blick auf ein nützliches Werkzeug im Angular-Werkzeugkasten werfen: die Content Projection mit <ng-content/>.

Was ist Content Projection?

Im Angularkontext beschreibt Content Projection oder auf Deutsch Inhaltsprojektion eine Technik für das Einfügen von Inhalten, ausgehend von einer übergeordneten Elternomponente, in eine untergeordnete Kindkomponente 1.

Im Vergleich zum @Input-Dekorator bietet Content Projection den Vorteil, dass eine größere Vielfalt an Elementen in die Kind-Komponente übergeben werden kann.

Mögliche Inhalte:

  • Inner HTML
  • HTML Elemente
  • Gestylte HTML-Elemente
  • Andere Angular-Komponenten

Beispiel

Um sicherzustellen, dass wir alles verstehen, schauen wir uns ein praktisches Beispiel an.
In der folgenden app.component.ts-Datei haben wir eine Elternkomponente, die die HeadlineComponent (Kindkomponente) verwendet.

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [HeadlineComponent],
  template: '<app-headline>Example Headline</app-headline>',
  styleUrl: './app.component.scss'
})
export class AppComponent {}

Der Inhalt Example Headline wird mithilfe von Content Projection in die <app-headline>-Komponente projiziert.

@Component({
  selector: 'app-headline',
  standalone: true,
  imports: [],
  template: '<h1><ng-content></ng-content></h1>',
  styleUrl: './headline.component.scss'
})
export class HeadlineComponent {}

Das Resultat im DOM ist ein <h1>-Tag, mit Example Headline als Inhalt.

<h1>Content Projection</h1>

Falls du dir den Code selbst anschauen möchtest, findest du ihn in diesem Repository, auf dem examples/basic Branch.

Mehrfache Platzhalter

Manchmal ist es notwendig, komplexere Layouts zu erstellen, die unterschiedliche Teile von Inhalten an verschiedenen Stellen rendern. Angular bietet hierfür die Möglichkeit, mehrere Inhalte in eine Komponente zu injizieren, indem man mehrere <ng-content>-Platzhalter verwendet.
Die unten stehenden Beispiele sind ebenfalls bei GitHub zu finden.

Komponente mit mehreren <ng-content>-Elementen:

<div class="card">
  <h1>
    <ng-content select="[card-title]"></ng-content>
  </h1>
  <ng-content select="card-body"></ng-content>
  <ng-content select=".card-footer"></ng-content>
</div>

Wir erkennen das uns bekannte <ng-content>.
Hier wird das select-Attribut verwendet, um spezifische Inhalte in die entsprechenden Platzhalter zu projizieren. Beispiel für die Verwendung:

<app-custom-card>
  <card-title>Hello</card-title>
  <card-body>Welcome to the example</card-body>
</app-custom-card>

Zuordnung der Projektion

Es gibt mehrere Möglichkeiten zu bestimmen, welches <ng-content>-Element verwendet wird.

Selektorauswahl

Zum einen können wir, wie im oberen Beispiel, mit <card-title> und <card-body> die select-Attribute des jeweiligen <ng-content> referenzieren.
Es ist wichtig zu beachten, dass die Elternkomponente CUSTOM_ELEMENTS_SCHEMA als schemas definiert. Da Angular sonst nach einem Element mit dem Selektor <card-title> suchen würde. Wir teilen Angular also mit, dass es sich um valide Elemente handelt.

@Component({
  ...
  schemas: [CUSTOM_ELEMENTS_SCHEMA]
})

Div-Attribut

<ng-content select="[card-title]"></ng-content>
<div card-title>
  Card Title
</div>

Klasse/Id-Selektor

<ng-content select=".card-title"></ng-content>
<div class="card-title">
  Card Title
</div>

Zusammenfassung

Bis hierhin haben wir die Grundlagen der Content Projection in Angular behandelt und anhand von Beispielen gezeigt, wie Inhalte von einer Elternkomponente in eine Kindkomponente projiziert werden können.
Wir haben die einfache Verwendung von <ng-content> erläutert und gesehen, wie man mehrere Platzhalter in einer Komponente definiert, um verschiedene Inhalte gezielt einzufügen.

Im Folgenden legen wir den Fokus darauf, wann es Sinn macht, diese Technik zu nutzen.
Denn nur weil wir mit <ng-content> sprichwörtlich einen Hammer in der Werkezugkiste von Angular gefunden haben, bedeutet dies nicht, dass nun alles ein Nagel ist.

Wann macht ng-content Sinn?

  • Bei komplexeren Layouts. Durch die Nutzung von <ng-content> kann die erstellte Komponente verschiedenste Inhalte aufnehmen (Siehe Card-Beispiel).
  • Wenn du die Daten in der Elternkomponente behalten möchtest und die Inhalte durch die Kinder nur angezeigt werden sollen. So sparst du dir @Input() und @Output(). Dies macht deinen Code übersichtlicher, hält Datenströme kurz und ist wartbarer für dich und dein Team.
  • Falls es dein Ziel ist, eine geringere Kopplung von Eltern- und Kindkomponenten zu erreichen.
  • Einfachere Unterteilung in Smarte- und Präsentations-Komponenten
  • Wenn eine Komponente zusätzliche Inhalte von einer Elternkomponente anzeigen soll, du aber die ursprüngliche Logik nicht verändern willst.
  • Wenn du direkt HTML projizieren möchtest.

Wann sollte ich auf ng-content verzichten?

  • Statische Inhalte. Wenn sich der Inhalt nicht ändert bietet die Nutzung von <ng-content> keinen Vorteil.
  • Wenn du Inputdaten typisieren oder transformieren möchtest, dies erreichst du mit klassischen Inputs leichter.

Was sollte ich noch über ng-content wissen?

Neben den grundlegenden Anwendungsfällen gibt es noch einige zusätzliche Aspekte von <ng-content>, die du kennen solltest.

  1. <ng-content> sollte nicht bedingt mit @If etc. eingebunden werden, da Angular die DOM-Elemente auch für die nicht sichtbaren Platzhalter erstellt 1.

  2. Es ist möglich, einen Default-Value für <ng-content> zu setzen. Dieser wird, wie in dem Beispiel weiter unten, zwischen das öffnende und schließende ng-content-tag gesetzt.
    Der Default wird angezeigt, wenn es keinen Inhalt gibt, der dem angegebenen select eines <ng-content> entspricht.

<div class="card">
   <h1>
    <ng-content select="[card-title]">DEFAULT-Value</ng-content>
   </h1>
   <ng-content select="card-body"></ng-content>
   <ng-content select=".card-footer"></ng-content>
</div>
  1. Weiterhin ist es möglich ein fallback <ng-content> zu nutzen. Wenn wir wieder auf das Card-Beispiel schauen, sehen wir, dass es mehrere ng-content-Blöcke mit einem select gibt. Versucht die Elternkomponente Inhalt zu projizieren, der nicht einem der select entspricht, wird dieser nicht angezeigt.
    Dieses Problem lässt sich durch ein einfaches Hinzufügen von <ng-content/> beheben. Der projizierte Inhalt der keinem select zugeordnet werden kann, wird in diesen fallback <ng-content></ng-content> proijiziert.

  2. Mithilfe von ngProjectAs können wir einen CSS-Selektor angeben. Immer wenn ein Element mit ngProjectAs gegen einen <ng-content>-Platzhalter geprüft wird, vergleicht Angular mit dem ngProjectAs-Wert anstelle der Identität des Elements. In diesem Fall wird der card-body als ein h3 HTML-Element gerendert werden 1.

<app-custom-card>
  <div card-title>
    Title
  </div>
  <h3 ngProjectAs="card-body">Hello</h3>
  <div class="card-footer">Footer</div>
</app-custom-card>
  1. Bei dem <ng-content>-Element handelt es sich weder um eine Komponente, noch um ein DOM-Element. Es ist ein spezieller Platzhalter, der Angular aufzeigt, wo Inhalte gerendert werden sollen.
    Diese Elemente werden zur Build-Zeit verarbeitet. Daraus folgt, dass diese zur Laufzeit nicht manipuliert werden können.

Quellen


  1. Angular: Content projection with ng-content, in: Angular , o.D., https://angular.dev/guide/components/content-projection (abgerufen am 26.08.2024). ↩︎ ↩︎ ↩︎