Write Angular like a pro. Angular Icon

Follow the ultimate Angular roadmap.

Transclusion in Angular 2 with ng-content

Transclusion is an AngularJS (1.x) term, lost in the rewrite of Angular (v2+), so let’s bring it back for this article just concept clarity. The word “transclusion” may be gone, but the concepts remain.

Essentially, transclusion in AngularJS is/was taking content such as a text node or HTML, and injecting it into a template at a specific entry point.

This is now done in Angular through modern web APIs such as Shadow DOM and known as “Content Projection”. Let’s explore!

AngularJS transclusion

For those coming from an AngularJS background, transclusion looks a little like this with the .directive() API (if you know this already please pass Go and collect £200):

Single-slot transclusion

In AngularJS, we can designate a single slot to transclude content into:

function myComponent() {
  scope: {},
  transclude: true,
  template: `
   <div class="my-component">
      <div ng-transclude></div>
    </div>
  `
};
angular
  .module('app')
  .directive('myComponent', myComponent);

We can then use the Directive as follows:

<my-component>
  This is my transcluded content!
</my-component>

The compiled HTML output would then evaluate to:

<div class="my-component">
  <div>
    This is my transcluded content!
  </div>
</div>

Multi-slot transclusion

We can also define multiple entry points in AngularJS 1.5+ using an Object as the value:

function myComponent() {
  scope: {},
  transclude: {
    slotOne: 'p',
    slotTwo: 'div'
  },
  template: `
    <div class="my-component">
      <div ng-transclude="slotOne"></div>
      <div ng-transclude="slotTwo"></div>
    </div>
  `
};
angular
  .module('app')
  .directive('myComponent', myComponent);

Directive usage would be matching 'p' and 'div' tags in the above example to the relevant slots:

<my-component>
  <p>
    This is my transcluded content!
  </p>
  <div>
    Further content
  </div>
</my-component>

Evaluated DOM output:

<my-component>
  <div class="my-component">
    <div ng-transclude="slotOne">
      <p>
        This is my transcluded content!
      </p>
    </div>
    <div ng-transclude="slotTwo">
      <div>
        Further content
      </div>
    </div>
  </div>
</my-component>

Angular Content Projection

So now we know what we’re looking at from an AngularJS perspective, we can easily migrate this concept to Angular. However, if you’ve not used AngularJS, fear not as this concept is easily demonstrated above on how to inject content into another element or Component.

Angular Directives In-Depth eBook Cover

Free eBook

Directives, simple right? Wrong! On the outside they look simple, but even skilled Angular devs haven’t grasped every concept in this eBook.

  • Green Tick Icon Observables and Async Pipe
  • Green Tick Icon Identity Checking and Performance
  • Green Tick Icon Web Components <ng-template> syntax
  • Green Tick Icon <ng-container> and Observable Composition
  • Green Tick Icon Advanced Rendering Patterns
  • Green Tick Icon Setters and Getters for Styles and Class Bindings

Web Components

In Web Components, we had the <content> element, which was recently deprecated, which acted as a Shadow DOM insertion point. Angular allows Shadow DOM through the use of ViewEncapsulation. Early alpha versions of Angular adopted the <content> element, however due to the nature of a bunch of Web Component helper elements being deprecated, it was changed to <ng-content>.

Single-slot content Projection

In Angular’s single-slot content projection, the boilerplate is so much nicer and more descriptive. We simply use the <ng-content> element in our Component and that’s it:

// my-component.component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'my-component',
  template: `
   <div class="my-component">
      <ng-content></ng-content>
    </div>
  `
})
export class MyComponent {}

Now to use the element we import MyComponent, and project some content between those <my-component> tags:

// app.component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'my-app',
  template: `
    <div class="app">
      <my-component>
        This is my transcluded content!
      </my-component>
    </div>
  `
})
export class AppComponent {}

DOM output:

<div class="app">

  <div class="my-component">
    This is my transcluded content!
  </div>

</div>

Live output:

Multi-slot content projection

Multi-slot is just as easy as you’d think as well. Much like multi-slot in AngularJS, we use named slots again. However the only difference is instead of aliasing the DOM reference against a custom transclude: {} property, we talk to the DOM node directly.

Let’s assume the following markup inside our my-app Component:

// app.component.ts
@Component({
  selector: 'my-app',
  template: `
    <div class="app">
      <my-component>
        <my-component-title>
          This is the Component title!
        </my-component-title>
        <my-component-content>
          And here's some awesome content.
        </my-component-content>
      </my-component>
    </div>
  `
})

Here we’re assuming we have my-component-title and my-component-content available as custom components. Now we can grab references to the components and tell Angular to inject where appropriate.

The only change we need to make from AngularJS thinking is adding a dedicated select="" attribute to the <ng-content> element:

// my-component.component.ts
@Component({
  selector: 'my-component',
  template: `
    <div class="my-component">
      <div>
        Title:
        <ng-content select="my-component-title"></ng-content>
      </div>
      <div>
        Content:
        <ng-content select="my-component-content"></ng-content>
      </div>
    </div>
  `
})

This internally fetches the relevant DOM node, which in this case are <my-component-title> and <my-component-content>.

DOM output:

<div class="app">

  <div class="my-component">
    <div>
      Title:

        This is the Component title!

    </div>
    <div>
      Content:

        And here's some awesome content.

    </div>
  </div>

</div>

Live output:

We don’t have to use a custom element approach as above when declaring content to be projected, we can use regular elements and target them the way we talk to elements with document.querySelector:

// app.component.ts
@Component({
  selector: 'my-app',
  template: `
    <div class="app">
      <my-component>
        <div class="my-component-title">
          This is the Component title!
        </div>
        <div class="my-component-content">
          And here's some awesome content.
        </div>
      </my-component>
    </div>
  `
})

And corresponding template changes inside MyComponent:

// my-component.component.ts
template: `
  <div class="my-component">
    <div>
      Title:
      <ng-content select=".my-component-title"></ng-content>
    </div>
    <div>
      Content:
      <ng-content select=".my-component-content"></ng-content>
    </div>
  </div>
`

Thank you for reading!

Learn Angular the right way.

The most complete guide to learning Angular ever built.
Trusted by 82,951 students.

Todd Motto

with Todd Motto

Google Developer Expert icon Google Developer Expert

Related blogs 🚀

Free eBooks:

Angular Directives In-Depth eBook Cover

JavaScript Array Methods eBook Cover

NestJS Build a RESTful CRUD API eBook Cover