In this article, we’ll explore how to use ng-Content for content projection to create flexible reusable components.

ng-content

The ng-content element is a placeholder for inserting external or dynamic content. The parent component passes the external content to the child component, and when Angular parses the template, it inserts the external content where the ng-Content appears in the child component template.

We can use content projections to create reusable components. These components have similar logic and layout and can be used in many places. This is often used when encapsulating common components.

Do not use content projection

To understand why ng-content is used for content projection, let’s first create a button component that is very common.

btn.component.ts

import { Component } from '@angular/core';

@Component({
  selector: 'app-btn'.templateUrl: './btn.component.html'.styleUrls: ['./btn.component.scss'],})export class BtnComponent {
  constructor() {}

  onClick($event: any) {
    console.log($event); }}Copy the code

btn.component.html

<button (click) =onClick($event)>
  Click Me
</button>
Copy the code

In this component, the button text is always Click Me. What if we want to pass different text in? You might think of the most commonly used @Input decorator, but what if we wanted to pass in HTML instead of just text? This is where the main character of this article comes in: ng-content.

Single slot content projection

The most basic form of content projection is the single-slot content projection. Single-slot content projection refers to creating a component into which we can project a component.

To create a component that uses a single-slot content projection, we just need to make some simple changes to the above component: replace Click Me with
.

btn.component.html

<button (click) =onClick($event)>
  <ng-content></ng-content>
</button>
Copy the code

Where BTN components are used:

<app-btn>Cancel</app-btn>
<app-btn><b>Submit</b></app-btn>
Copy the code

The content in < app-bTN >
is passed to the BTN component and displayed in ng-contnet.

Multi-slot content projection

The BTN component above is very simple, but ng-Content is actually more powerful than this. A component can have multiple slots, and each slot can specify a CSS selector that decides what to put in the slot. This mode is called multi-slot content projection. With this pattern, we must specify where we want the projected content to appear. You can do this by using the select attribute of ng-content.

To create a component that uses a multi-slot content projection, do the following:

  1. Create a component.
  2. In the component template, addng-contentElement in which you want the projected content to appear.
  3. willselectAttribute added tong-contentElements. Angular uses selectors that support tag names, attributes, CSS classes, and:notAny combination of pseudo-classes.

So let’s create a card component that’s a little bit more complicated.

card.component.html

<div class="card">
  <div class="header">
    <ng-content select="header"></ng-content>
  </div>
  <div class="content">
    <ng-content select="content"></ng-content>
  </div>
  <div class="footer">
    <ng-content select="footer"></ng-content>
  </div>
</div>
Copy the code

Where the card component is used:

app.component.html

<app-card>
  <header>
    <h1>Angular</h1>
  </header>
  <content>One framework. Mobile & desktop.</content>
  <footer><b>Super-powered by Google </b></footer>
</app-card>

<app-card>
  <header>
    <h1 style="color:red;">React</h1>
  </header>
  <content>A JavaScript library for building user interfaces</content>
  <footer><b>Facebook Open Source </b></footer>
</app-card>
Copy the code

What if I have something in my app-card that’s not header, content, footer? For example, use the app-card component as follows:

app.component.html

<app-card>
  <header>
    <h1>Angular</h1>
  </header>
  <div>Not match any selector</div>
  <content>One framework. Mobile & desktop.</content>
  <footer><b>Super-powered by Google </b></footer>
  <div>This text will not not be shown</div>
</app-card>
Copy the code

We’ll see that neither div is rendered on the page. To solve this problem, we can add a ng-content tag to the component without any selector. Anything that can’t be matched to any other slot will be rendered in this one.

card.component.html

<div class="card">
  <div class="header">
    <ng-content select="header"></ng-content>
  </div>
  <div class="content">
    <ng-content select="content"></ng-content>
  </div>
  <div class="footer">
    <ng-content select="footer"></ng-content>
  </div>
  <ng-content></ng-content>
</div>
Copy the code

ngProjectAs

In some cases, we need to use ng-Container to wrap some content around and pass it to the component. Most of the time this is because we need to use structural directives like ngIf or ngSwitch. For example, you only pass the header to the card component under certain circumstances.

In the following example, we wrap the header in ng-Container.

<app-card>
  <ng-container>
    <header>
      <h1>Angular</h1>
    </header>
  </ng-container>
  <content>One framework. Mobile & desktop.</content>
  <footer><b>Super-powered by Google </b></footer>
</app-card>
Copy the code

Because of the ng-Container, the header part is not rendered into the slot we want to render, but into the ng-Content that doesn’t provide any selector.

In this case, we can use the ngProjectAs attribute.

Add this property to the ng-container above to render as expected.

<app-card>
  <ng-container ngProjectAs='header'>.</app-card>
Copy the code