Lesson 9 of 30
ng-content lets a parent hand a component actual markup to render inside itself — Angular's answer to React's children prop, with named slots built in.
12 min read
Every option explains itself — why the right one is right, and why each wrong one is wrong. Miss one and it comes back later on a spaced-repetition schedule.
This topic is about judgment and practical tradeoffs more than a single right answer — a mock interview is the better way to test yourself on it.
Be interviewed on this →Hints without spoilers, and a plain explanation of why a wrong answer was wrong — answered only from this lesson. Free account, no card.
Sign up freeWhat interviewers actually ask about this, and what a strong answer has to cover.
A strong answer covers
Likely follow-ups
Most component inputs pass data — a string, a number, an object. But sometimes you want to pass markup itself: a card component that doesn't know or care what goes inside it, just that something should render in its body. That's content projection.
You mark a spot in a component's template with <ng-content />, and whatever markup the parent writes between that component's opening and closing tags gets projected into that spot. The child component doesn't own or control that markup — it doesn't even know what it is — it just reserves a place for it. This is different from an input: an input is data the child reads and can transform; projected content is markup the parent authored, rendered as-is, inside the child's layout.
A single <ng-content /> handles the simple case, but real components usually need more structure — a header slot, a body slot, a footer slot, all filled independently. That's what the select attribute is for.
import { Component } from '@angular/core';
@Component({
selector: 'app-card',
standalone: true,
template: `
<section class="card">
<header class="card-header">
<ng-content select="[card-title]" />
</header>
<div class="card-body">
<ng-content />
</div>
@if (true) {
<footer class="card-footer">
<ng-content select="[card-actions]" />
</footer>
}
</section>
`,
})
export class CardComponent {}
Used from a parent:
<app-card>
<h2 card-title>Deploy failed</h2>
<p>Build #482 failed at the test stage. Check the logs for details.</p>
<div card-actions>
<button>View logs</button>
<button>Retry</button>
</div>
</app-card>
The select="[card-title]" attribute tells Angular: "anything the parent marks with a card-title attribute gets projected here, specifically." Content that doesn't match any select falls through to the plain <ng-content /> — that's why the <p> lands in .card-body even though nothing marked it explicitly. select can also match a tag name (select="h2") or a CSS class (select=".summary"), not just an attribute — attribute selectors are just the most common convention because they don't force the parent into a specific element type.
Conditional projection — showing the footer only if the parent actually provided action content — is trickier than it looks, because <ng-content> always renders its slot's projected nodes if they exist in the DOM, whether or not they're visually meaningful. In practice, teams either always render the slot's wrapper (as above) and let CSS collapse an empty one, or check for content in the component class using @ContentChild/contentChild() to query whether a queryable element was actually projected before deciding to render the wrapper at all.
<ng-content /> tags don't split content between them. If a component has two plain <ng-content /> calls with no select, all unmatched projected content goes to the first one; the second gets nothing. Split content across true slots by giving each one a distinct select.ng-content is not a component and never renders its own DOM element. It's a placeholder the compiler resolves at build time — inspecting the DOM at runtime, you'll never actually see an <ng-content> tag, only whatever was projected into it (or nothing, if nothing was).*ngIf-style local component state until you explicitly query it. The child can't just "look inside" what was projected the way it can read its own template variables — that's exactly why contentChild() / @ContentChild exist as separate query APIs.This is Angular's version of React's children prop — a parent handing a component markup to render, not just data. The named-slot mechanism (select="[card-title]") goes further than plain children does out of the box, closer to what a React developer would build manually by accepting several distinct prop-passed-JSX values (header, footer) rather than one opaque children blob.