Skip to the solution
MediumFrontend8 min read

What is content projection and ng-content?

81 views

The problem

Question presented to candidate: "We want a reusable <app-callout> that draws a styled frame, while each page decides what goes inside. How does content projection with ng-content work, and what are the gotchas?"

What a strong answer should cover:

  • Content projection passes markup written between a component's tags (its content) into a spot in that component's template marked by <ng-content>.
  • The projected content is created and owned by the parent: its bindings use the parent's state and update with the parent's change detection.
  • <ng-content> is a placeholder, not a real element or a directive; it cannot be bound, styled or repeated.
  • Fallback content (v18+): markup inside <ng-content>...</ng-content> shows when nothing is projected.
  • Gotcha: wrapping <ng-content> in @if hides content but does not prevent it from being created; use ng-template + ngTemplateOutlet or @defer for truly lazy content.
  • Projected content lands once: two <ng-content> for the same content means only the last receives it.

Clarifying questions expected:

  • "Does the wrapper need several distinct areas (header, body, footer)? That would be multi-slot projection."
  • "Should hidden content be created lazily, or is hiding enough?"

Code / implementation expected: Yes β€” a wrapper component using ng-content, ideally with fallback content.

  • content-projection

Try it yourself

Sketch your approach before you read the solution. That is what the interview tests.

Stuck? Get a hint.

A short nudge toward the approach. It will not give the answer away.

Solution

The solution is hidden so you can try first

8 min read Β· 10 sections

Read the code

AngularA wrapper with ng-content and fallback content, and the projected-child lifecycle trap
import { Component, signal } from "@angular/core";

// A reusable wrapper: it draws the frame, the parent supplies the inside.
@Component({
  selector: "app-callout",
  template: `
    <div class="callout" style="border: 2px solid teal; padding: 8px; margin: 6px 0">
      <ng-content>Nothing to report.</ng-content>
    </div>
  `,
})
export class CalloutComponent {}

// A projected child that announces when it is created.
@Component({ selector: "app-chart", template: `<i>[chart]</i>` })
export class ChartComponent {
  constructor() {
    console.log("ChartComponent created");
  }
}

// Hides its projected content, but does NOT stop it being created.
@Component({
  selector: "app-collapsible",
  template: `
    <button (click)="open.set(!open())">{{ open() ? "Collapse" : "Expand" }}</button>
    @if (open()) {
      <ng-content />
    }
  `,
})
export class CollapsibleComponent {
  open = signal(false);
}

@Component({
  selector: "app-root",
  imports: [CalloutComponent, ChartComponent, CollapsibleComponent],
  template: `
    <app-callout>Deploy finished in <b>{{ seconds() }}s</b>.</app-callout>
    <app-callout />
    <app-collapsible><app-chart /></app-collapsible>
    <button (click)="seconds.set(seconds() + 1)">Slower deploy</button>
  `,
})
export class AppComponent {
  seconds = signal(42);
}

Discussion

Other approaches, follow-ups and how it went in real interviews.

Sign in to join the discussion.

No comments yet. Share how you would answer it.

Question 78 of 121 in Angular

Keep your momentum going.

Mark this question solved to track your progress, then take the next one while it is fresh.

All Angular questions
Up nextHard

What is the difference between AOT and JIT compilation?

Continue