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