Content projection lets a wrapper component define structure while its caller supplies markup through ng-content. Learn selector matching, fallback behavior, query timing, and the difference between projected content and a child component input.
Content projection lets a component own a visual shell while its caller supplies markup for part of that shell. The receiving component places ng-content in its template; Angular inserts matching caller content at that location.
Projected markup keeps the template context of the caller. An expression inside the projected markup can read the parent component, not private fields of the receiving wrapper. Use inputs for data the wrapper must understand and projection for markup the caller should control.
// card.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-card',
template: `<section class="card"><ng-content /></section>`
})
export class CardComponent {}
// parent template
<app-card>
<h2>{{ productName }}</h2>
<button type="button" (click)="addToCart()">Add</button>
</app-card>
The heading and button render inside the card section. productName and addToCart belong to the parent component.
The card controls the section wrapper, while the caller controls the projected heading and action. Event and property expressions still execute in the caller context.
A component can define named slots with select. Angular matches projected nodes against each CSS selector and inserts them into the corresponding ng-content location. Keep selectors simple and document which slots are required or optional.
An unselected ng-content acts as the catch-all slot. Put it last so unmatched body content has a predictable destination. Content that matches no slot and has no catch-all is not rendered.
@Component({
selector: 'app-card',
template: `
<article>
<header><ng-content select="[card-title]" /></header>
<div class="body"><ng-content /></div>
<footer><ng-content select="[card-actions]" /></footer>
</article>
`
})
export class CardComponent {}
// parent template
<app-card>
<h2 card-title>Delivery</h2>
<p>Arrives in two business days.</p>
<button card-actions type="button">Track order</button>
</app-card>
The two attribute selectors route the title and button to named slots. The paragraph does not match either selector, so the catch-all slot receives it.
Do not conditionally include ng-content with @if, @for, or @switch. Angular always instantiates content intended for projection even when the placeholder is hidden. If content creation itself must be conditional or repeated, pass a TemplateRef and render it with ngTemplateOutlet instead.
Projection is not a replacement for component APIs. If the wrapper must validate, sort, serialize, or persist a value, accept typed data through an input and reserve projection for presentation.
Content queries let the receiving component find directives or components supplied through projection. contentChild returns one reactive query result; contentChildren returns an array signal. Queries do not pierce component boundaries.
Prefer querying a directive or component type over raw markup. A marker directive creates an explicit contract and gives the wrapper a typed API to inspect.
import { Component, Directive, contentChild } from '@angular/core';
@Directive({ selector: '[dialogPrimaryAction]' })
export class DialogPrimaryActionDirective {
disabled = false;
}
@Component({
selector: 'app-dialog-shell',
template: `<ng-content /><ng-content select="[dialogPrimaryAction]" />`
})
export class DialogShellComponent {
primaryAction = contentChild(DialogPrimaryActionDirective);
}
The marker directive serves both as a slot selector and as a typed query target. Because the query is optional, the dialog can render without a primary action.
// caller template
<ng-template #emptyState let-query="query">
<p>No results for {{ query }}.</p>
</ng-template>
<app-search-results [emptyTemplate]="emptyState" />
// receiving component field and template fragment
emptyTemplate = input.required<TemplateRef<{ query: string }>>();
@if (results().length === 0) {
<ng-container
[ngTemplateOutlet]="emptyTemplate()"
[ngTemplateOutletContext]="{ query: searchTerm() }" />
}
TemplateRef is appropriate when the receiving component decides whether, when, or how often caller-provided markup is instantiated. Plain ng-content is the simpler choice when content is always projected.
Angular compiles projected markup in the caller context where it is written, not in the receiving component context. Pass wrapper-owned values through an explicit input or design a TemplateRef context when the caller needs them.
Check each select value against the projected element or component selector. Add an unselected catch-all slot for body content that should always render, and use ngProjectAs only for a static selector override.
Projected content is not initialized when the receiving component constructor runs. A decorator-based ContentChild query becomes reliably available in ngAfterContentInit, after Angular has inserted and queried the external content.
Explore 500+ free tutorials across 20+ languages and frameworks.