Tutorials Logic, IN info@tutorialslogic.com

Angular Content Projection ng content Slots

Projection Contract

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.

  • Use projection for reusable shells such as cards, dialogs, panels, and toolbar layouts.
  • Use an input when the child needs a typed value to calculate behavior rather than arbitrary markup.
  • ng-content is a compile-time placeholder, not a component or DOM element you instantiate at runtime.

Single-slot Card

Single-slot Card
// 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>
Output
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.

Projection Workflow

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.

  • select="[card-title]" matches an element carrying the card-title attribute.
  • select="app-card-actions" matches a component element by selector.
  • ngProjectAs can make an element match a static slot selector when changing its actual element is impractical.

Named Card Slots

Named Card Slots
@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.

Projection Mistakes

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.

  • Symptom: content disappears. Fix: verify the element matches select exactly or add a catch-all slot.
  • Symptom: an expression cannot read the wrapper field. Fix: expose data through the parent context, an input, or a template context API.
  • Symptom: a required ContentChild query fails. Fix: guarantee the matching projected child or use the optional query result.

Content Projection Design

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.

  • Use contentChild.required only when the component contract guarantees a match.
  • Signal queries update when projected content changes, including content controlled by @if.
  • Decorator-based ContentChild is reliably initialized by ngAfterContentInit; signal queries can be consumed reactively.

Typed Projected Action

Typed Projected Action
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.

TemplateRef for Conditional Content

TemplateRef for Conditional Content
// 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.

Confirm the page outcome

Projection Review

3 checks
  • I can choose between an input, ordinary ng-content, named slots, and a TemplateRef based on who owns data and instantiation.
  • I can explain caller template context and diagnose unmatched projected content.
  • I can query projected directives with contentChild or contentChildren without crossing component boundaries.

Projection Review Questions

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.

Browse Free Tutorials

Explore 500+ free tutorials across 20+ languages and frameworks.