Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

Content projection: ng-content

Inputs pass data into a component, but sometimes you need to pass a whole piece of markup: a heading with an icon, a form, a list. This is what content projection is for — the <ng-content> tag marks the spot where Angular inserts everything written between the component's opening and closing tags.

A wrapper component

Let's make a card with a shadow that can hold anything:

ng generate component card
<!-- src/app/card/card.html -->
<div class="card">
  <ng-content />
</div>
/* src/app/card/card.css */
.card {
  padding: 16px;
  border-radius: 8px;
  box-shadow: 0 2px 6px rgb(0 0 0 / 0.15);
}

The Card class in src/app/card/card.ts stays empty — all the work is in the template.

Using it

The parent imports Card and writes the content inside the tag:

<!-- src/app/app.html -->
<app-card>
  <h2>To-do list</h2>
  <app-todo-list />
</app-card>
// src/app/app.ts
import { Component } from '@angular/core';
import { Card } from './card/card';
import { TodoList } from './todo-list/todo-list';
 
@Component({
  selector: 'app-root',
  imports: [Card, TodoList],
  templateUrl: './app.html',
  styleUrl: './app.css',
})
export class App {}

The heading and the to-do list end up inside the box with a shadow — in place of <ng-content />. Without that tag the content simply does not appear on the page.

Multiple slots

The select attribute sets which elements go into a given slot. Its value is a CSS selector:

<!-- src/app/card/card.html -->
<div class="card">
  <header class="card-header">
    <ng-content select="[card-title]" />
  </header>
  <ng-content />
</div>
<!-- src/app/app.html -->
<app-card>
  <h2 card-title>To-do list</h2>
  <app-todo-list />
</app-card>

The element with the card-title attribute goes into the card header, and everything else into the slot without select. The card-title attribute does nothing by itself — it is just a marker for the selector.

How it differs from input()

ApproachWhat it passesExample
input()Data: strings, numbers, objectsA task for app-todo-item
ng-contentReady-made markup and componentsThe contents of a card

A component with ng-content does not know what will be put inside it — that is why it suits wrappers: cards, modal windows, tabs.

Common mistake

Expecting an @if around <ng-content /> in the child to prevent the content from being created. Components inside the <app-card> tag are created by the parent, and they are always created, even if the slot is hidden. If a heavy component is not always needed, wrap the component itself in @if — in the parent's template.

Thanks for reading!

Donate to the site — we work hard for you.