Architecture Pattern:

Architecture Pattern: "Compound Components" and Advanced Projection with `ngProjectAs`

posted 1 min read

When building a Design System or a library of shared components, the most difficult challenge is balancing consistency with flexibility.

You want all your Modals to look the same (consistency), but some Modals contain forms, others contain text, and others contain complex grids (flexibility).

The Architectural Pattern that solves this is Content Projection (specifically Multi-Slot Projection).

The Basics: The "Select" API

When you build a container, you define its API not by Inputs, but by Slots.

@Component({
  selector: 'app-layout',
  template: `
    <header>
      <ng-content select="nav"></ng-content>
    </header>
    <main>
      <ng-content></ng-content>
    </main>
  `
})
export class AppLayoutComponent {}

The Senior "Gotcha": Wrapper Elements

Here is a problem 99% of devs hit.

Imagine your app-layout expects a <nav> tag because of select="nav".

But you want to project a custom component called <app-top-bar>.

<app-layout>
  <app-top-bar></app-top-bar>
</app-layout>

Your app-top-bar will fall into the default slot (Main), not the Header slot.

The Junior Fix:
Wrap it in a nav tag.

<app-layout>
  <nav> <app-top-bar></app-top-bar>
  </nav>
</app-layout>

Problem: You just introduced an extra DOM node (<nav>) that might mess up your Flexbox or Grid layout.

The Senior Fix: ngProjectAs
Angular has a special attribute that allows you to "disguise" an element so it matches a specific selector, without adding extra DOM nodes.

<app-layout>
  <app-top-bar ngProjectAs="nav"></app-top-bar>
</app-layout>

This tells Angular: "Treat this component as if it were a <nav> tag for the purposes of projection."

The "Projected Content" Lifecycle Risk

As a Senior Developer, you need to know the performance cost.

Projected content is instantiated EAGERLY.

If you project a heavy component:
<app-modal><app-heavy-chart></app-heavy-chart></app-modal>

The app-heavy-chart is created, its constructor runs, and its ngOnInit runs immediately, even if the modal is hidden via display: none.

The Optimization:
If the content is extremely heavy, do not use basic Content Projection. Use Template Projection (ng-template).

<app-modal [contentTemplate]="myChart"></app-modal>

<ng-template #myChart>
  <app-heavy-chart></app-heavy-chart>
</ng-template>
// Child (Modal)
@Input() contentTemplate: TemplateRef<any>;
<ng-container *ngTemplateOutlet="contentTemplate"></ng-container>

This ensures the chart is not instantiated until the Modal actually renders the template outlet.

Mastering these nuances—ngProjectAs and TemplateRef—is what separates a component library author from a component user.

1 Comment

0 votes

More Posts

Angular-Aware E2E Testing: Query Components by @Input and Signals in Playwright

vitalicset - Apr 2

Stop Treating Angular as a Second-Class Framework for UI Components

Karol Modelskiverified - Apr 16

The Senior Angular Take‑Home That Made Me Rethink Tech Interviews

Karol Modelskiverified - Apr 2

Deep Dive: Solving Race Conditions and Concurrency with RxJS Operators

Habib - Nov 24, 2025

The Senior Developer's Blueprint: Implementing Container vs. Presentational Architecture in Angular

Habib - Nov 22, 2025
chevron_left

Related Jobs

View all jobs →

Commenters (This Week)

3 comments
2 comments
1 comment

Contribute meaningful comments to climb the leaderboard and earn badges!