Skip to content

Commit 1112ef1

Browse files
Content projection (#95)
1 parent 7fe6f15 commit 1112ef1

1 file changed

Lines changed: 275 additions & 0 deletions

File tree

Lines changed: 275 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,275 @@
1+
---
2+
title: "Content Projection: Inhalte an Komponenten übergeben"
3+
published: 2026-05-15
4+
lastModified: 2026-05-15
5+
---
6+
7+
Wenn wir eine Komponente in unser Template einbinden, notieren wir sie üblicherweise ohne weiteren Inhalt zwischen dem öffnenden und schließenden Tag.
8+
Manchmal ist es aber sinnvoll, einer Komponente eigenes Markup oder auch andere Komponenten als Kindelemente mitzugeben – zum Beispiel bei Cards, Dialogen oder Layouts.
9+
Angular ermöglicht das mit **Content Projection** und dem Element `<ng-content>`.
10+
In diesem Artikel erklären wir das Konzept im Detail und zeigen, wie du es in deinen Komponenten einsetzen kannst.
11+
12+
## Inhalt
13+
14+
[[toc]]
15+
16+
## Was ist Content Projection?
17+
18+
Wer bereits mit anderen Frameworks wie Vue.js, Svelte oder mit Web Components gearbeitet hat, kennt dieses Konzept vielleicht unter dem Begriff _Slot_.
19+
Der Name stammt vom nativen [`<slot>`-Element](https://developer.mozilla.org/docs/Web/HTML/Element/slot) aus der Spezifikation für Web Components.
20+
In Angular heißt das Konzept _Content Projection_ (früher auch _Transclusion_ genannt) und funktioniert über das spezielle Element `<ng-content>`.
21+
22+
Die Grundidee: Wenn wir eine Komponente verwenden, können wir zwischen dem öffnenden und schließenden Tag beliebige Inhalte notieren.
23+
Diese Inhalte werden als _Content_ der Komponente bezeichnet.
24+
Die Komponente entscheidet dann in ihrem eigenen Template, _wo_ dieser Content dargestellt wird.
25+
26+
## Content einer Komponente
27+
28+
Um Komponenten in unsere Templates einzubinden, erstellen wir ein Host-Element, das zum Selektor der Komponente passt.
29+
Üblicherweise notieren wir dieses Element ohne weiteren Inhalt zwischen dem öffnenden und schließenden Tag – oder wir verwenden sogar ein Self-Closing Tag.
30+
Geben wir hingegen dort Inhalte an, sind sie zunächst nicht sichtbar, denn das Element wird vollständig mit dem Template der Komponente gefüllt.
31+
Der Beispieltext `Lorem ipsum dolor` wird im folgenden Beispiel also nicht dargestellt:
32+
33+
```html
34+
<my-component>Lorem ipsum dolor</my-component>
35+
```
36+
37+
Auch wenn der Content zunächst nicht sichtbar ist, so ist er nicht verloren:
38+
Wir können auf den Inhalt zugreifen und auf diese Weise selbstdefiniertes Markup an eine Komponente übergeben.
39+
Das ist vor allem sinnvoll für generische UI-Komponenten wie Widgets, Cards oder Layouts.
40+
41+
## Der Platzhalter `<ng-content>`
42+
43+
Verwenden wir den Platzhalter `<ng-content>` im Template unserer Komponente, wird an dieser Stelle der Content eingesetzt, der im Host-Element notiert ist.
44+
45+
```typescript
46+
@Component({
47+
selector: 'custom-card',
48+
template: `
49+
<div class="card-shadow">
50+
<ng-content />
51+
</div>
52+
`,
53+
})
54+
export class CustomCard {}
55+
```
56+
57+
Wenn wir die Komponente nun verwenden, wird der Content an der Stelle von `<ng-content>` gerendert:
58+
59+
```html
60+
<!-- Verwendung der Komponente -->
61+
<custom-card>
62+
<p>This is the projected content</p>
63+
</custom-card>
64+
65+
<!-- Gerendertes DOM -->
66+
<custom-card>
67+
<div class="card-shadow">
68+
<p>This is the projected content</p>
69+
</div>
70+
</custom-card>
71+
```
72+
73+
Angular bezeichnet alle Kindelemente einer Komponente, die auf diese Weise übergeben werden, als den _Content_ der Komponente.
74+
Das ist zu unterscheiden von der _View_ der Komponente, die sich auf die Elemente im eigenen Template bezieht.
75+
76+
### Wichtige Hinweise zu `<ng-content>`
77+
78+
Das Element `<ng-content>` ist weder eine Komponente noch ein DOM-Element.
79+
Es ist ein spezieller Platzhalter, der Angular mitteilt, wo Content gerendert werden soll.
80+
Der Angular-Compiler verarbeitet alle `<ng-content>`-Elemente zur Build-Zeit.
81+
Wir können `<ng-content>` zur Laufzeit nicht einfügen, entfernen oder verändern.
82+
Auch Direktiven, Styles oder beliebige Attribute können nicht auf `<ng-content>` angewendet werden.
83+
84+
> **Wichtig:** `<ng-content>` sollte nicht mit `@if`, `@for` oder `@switch` bedingt eingebunden werden.
85+
> Angular erzeugt immer DOM-Knoten für Content, der an einen `<ng-content>`-Platzhalter gerendert wird – auch wenn dieser Platzhalter versteckt ist.
86+
> Für bedingtes Rendern von Content sollte stattdessen [`ng-template`](https://angular.dev/api/core/ng-template) verwendet werden.
87+
88+
## Multi-Slot Projection
89+
90+
Um mehrere Abschnitte gezielt an verschiedenen Stellen im Template einzusetzen, unterstützt Angular die sogenannte _Multi-Slot Projection_.
91+
Der Platzhalter `<ng-content>` erhält dafür das Attribut `select`, in dem ein CSS-Selektor angegeben wird.
92+
Damit können wir einzelne Elemente aus dem Content gezielt "herausziehen" und an verschiedenen Stellen platzieren.
93+
94+
Nehmen wir an, wir binden eine Komponente ein und geben verschiedene HTML-Inhalte in ihrem Host-Element an:
95+
96+
```html
97+
<my-component>
98+
<h1>My Card</h1>
99+
<button (click)="doSth()">Action</button>
100+
<a href="/details">Link</a>
101+
This cat is awesome!
102+
</my-component>
103+
```
104+
105+
Im Template der Komponente können wir nun einzelne Teile des Contents gezielt auswählen und an verschiedenen Stellen einsetzen:
106+
107+
```html
108+
<div class="card">
109+
<ng-content select="h1"></ng-content>
110+
<div class="body">
111+
<ng-content></ng-content>
112+
</div>
113+
<div class="footer">
114+
<ng-content select="a,button"></ng-content>
115+
</div>
116+
</div>
117+
```
118+
119+
Im `select`-Attribut können wir die gleichen CSS-Selektoren verwenden wie bei [Selektoren von Komponenten](https://angular.dev/guide/components/selectors).
120+
Wir können auch mehrere Selektoren kombinieren (z. B. `select="a,button"`), um mehrere Elemente in denselben Slot zu projizieren.
121+
122+
Jedes Element aus dem Content kann nur _genau einmal_ eingesetzt werden.
123+
Wurde ein Element bereits von einem `select`-Selektor erfasst, steht es nicht für weitere Selektionen zur Verfügung.
124+
Ein `<ng-content>` ohne `select`-Attribut fängt dann den gesamten restlichen Content auf, der noch nicht von anderen Platzhaltern erfasst wurde.
125+
126+
Das obige Beispiel führt zum folgenden Ergebnis:
127+
128+
```html
129+
<div class="card">
130+
<h1>My Card</h1>
131+
<div class="body">This cat is awesome!</div>
132+
<div class="footer">
133+
<button (click)="doSth()">Action</button>
134+
<a href="/details">Link</a>
135+
</div>
136+
</div>
137+
```
138+
139+
Wenn eine Komponente kein `<ng-content>` ohne `select`-Attribut enthält, werden Elemente, die keinem Selektor entsprechen, nicht ins DOM gerendert.
140+
141+
## Fallback Content
142+
143+
Seit Angular 18 können wir für `<ng-content>` einen _Fallback-Inhalt_ definieren.
144+
Wird kein passender Content von außen übergeben, zeigt Angular stattdessen den Fallback an.
145+
Dazu notieren wir den Fallback-Inhalt als Kindelement von `<ng-content>`:
146+
147+
```html
148+
<!-- Template der Komponente -->
149+
<div class="card-shadow">
150+
<ng-content select="card-title">Standardtitel</ng-content>
151+
<div class="card-divider"></div>
152+
<ng-content select="card-body">Kein Inhalt vorhanden.</ng-content>
153+
</div>
154+
```
155+
156+
```html
157+
<!-- Verwendung -->
158+
<custom-card>
159+
<card-title>Hello</card-title>
160+
<!-- Kein card-body angegeben -->
161+
</custom-card>
162+
163+
<!-- Gerendertes DOM -->
164+
<custom-card>
165+
<div class="card-shadow">
166+
<card-title>Hello</card-title>
167+
<div class="card-divider"></div>
168+
Kein Inhalt vorhanden.
169+
</div>
170+
</custom-card>
171+
```
172+
173+
Fallback Content ist besonders nützlich für optionale Bereiche in generischen Komponenten, z. B. für einen Standard-Footer oder eine Platzhalter-Nachricht.
174+
175+
## Aliasing mit `ngProjectAs`
176+
177+
Angular unterstützt das spezielle Attribut `ngProjectAs`, mit dem wir ein Element unter einem anderen Selektor projizieren können.
178+
Wenn Angular ein Element mit `ngProjectAs` gegen einen `<ng-content>`-Platzhalter prüft, wird der Wert von `ngProjectAs` anstelle der tatsächlichen Identität des Elements verwendet:
179+
180+
```html
181+
<!-- Verwendung -->
182+
<custom-card>
183+
<h3 ngProjectAs="card-title">Hello</h3>
184+
<p>Welcome to the example</p>
185+
</custom-card>
186+
187+
<!-- Gerendertes DOM -->
188+
<custom-card>
189+
<div class="card-shadow">
190+
<h3>Hello</h3>
191+
<div class="card-divider"></div>
192+
<p>Welcome to the example</p>
193+
</div>
194+
</custom-card>
195+
```
196+
197+
Das `<h3>`-Element wird hier in den Slot für `card-title` projiziert, obwohl es kein `<card-title>`-Element ist.
198+
So können wir semantisch korrekte HTML-Elemente verwenden und trotzdem die Projektion steuern.
199+
200+
> **Hinweis:** `ngProjectAs` unterstützt nur statische Werte und kann nicht an dynamische Ausdrücke gebunden werden.
201+
202+
## Praxisbeispiel: eine Card-Komponente
203+
204+
Fassen wir das Gelernte in einem vollständigen Beispiel zusammen.
205+
Wir erstellen eine wiederverwendbare Card-Komponente mit Header, Body und Footer:
206+
207+
```typescript
208+
@Component({
209+
selector: 'app-card',
210+
template: `
211+
<article class="card">
212+
<header class="card-header">
213+
<ng-content select="[card-header]">Überschrift</ng-content>
214+
</header>
215+
<div class="card-body">
216+
<ng-content></ng-content>
217+
</div>
218+
<footer class="card-footer">
219+
<ng-content select="[card-footer]"></ng-content>
220+
</footer>
221+
</article>
222+
`,
223+
styles: `
224+
.card {
225+
border: 1px solid #ddd;
226+
border-radius: 8px;
227+
overflow: hidden;
228+
}
229+
.card-header {
230+
padding: 1rem;
231+
background: #f5f5f5;
232+
font-weight: bold;
233+
}
234+
.card-body {
235+
padding: 1rem;
236+
}
237+
.card-footer {
238+
padding: 1rem;
239+
background: #fafafa;
240+
display: flex;
241+
gap: 0.5rem;
242+
}
243+
`,
244+
})
245+
export class Card {}
246+
```
247+
248+
Die Verwendung sieht dann so aus:
249+
250+
```html
251+
<app-card>
252+
<h3 card-header>Meine Karte</h3>
253+
<p>Hier steht der Hauptinhalt der Karte.</p>
254+
<p>Er kann beliebig komplex sein.</p>
255+
<button card-footer (click)="save()">Speichern</button>
256+
<button card-footer (click)="cancel()">Abbrechen</button>
257+
</app-card>
258+
```
259+
260+
Hier verwenden wir Attributselektoren (`[card-header]`, `[card-footer]`), um die Slots zu definieren.
261+
Das hat den Vorteil, dass wir beliebige HTML-Elemente verwenden können und nicht auf spezifische Elementnamen angewiesen sind.
262+
263+
## Zusammenfassung
264+
265+
- **Content Projection** ermöglicht es, beliebiges Markup von außen an eine Komponente zu übergeben.
266+
- Das Konzept ist vergleichbar mit **Slots** in Web Components, Vue.js oder Svelte.
267+
- Der Platzhalter `<ng-content>` bestimmt, wo der Content im Template der Komponente gerendert wird.
268+
- Mit dem Attribut `select` auf `<ng-content>` können wir **Multi-Slot Projection** umsetzen und verschiedene Teile des Contents gezielt platzieren.
269+
- Jedes Element kann nur einmal projiziert werden. Ein `<ng-content>` ohne `select` fängt den restlichen Content auf.
270+
- **Fallback Content** (ab Angular 18) erlaubt es, Standardinhalte zu definieren, wenn kein passender Content übergeben wird.
271+
- Mit `ngProjectAs` können wir Elemente unter einem anderen Selektor projizieren.
272+
- `<ng-content>` sollte nicht mit `@if`, `@for` oder `@switch` bedingt eingebunden werden.
273+
274+
Content Projection ist ein mächtiges Werkzeug, um flexible und wiederverwendbare UI-Komponenten zu bauen.
275+
Es trennt die Struktur einer Komponente von ihrem konkreten Inhalt und macht sie dadurch universell einsetzbar.

0 commit comments

Comments
 (0)