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!
Table of contents
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.
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.
- Observables and Async Pipe
- Identity Checking and Performance
- Web Components <ng-template> syntax
- <ng-container> and Observable Composition
- Advanced Rendering Patterns
- 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!