Angular Directive & Transclude

Introduction

In my last post “Angular Directive”, I gave an introduction to directives. In this post, I want to extend the knowledge with transclude in directives.

Transclusion was not always easy for me and sometimes it still isn’t, but how it seems, I am not the only one out there :) I will try to explain it in a basic introductory manner. I will also provide a link which I think makes sense then for further reading where you will get a deeper understanding.

Transclusion is including content into the directive

Simply said, transclusion is to include the content in our directive into a template and generate then a result where everything is merged together.

Directive with transclusion

To be able to transclude the content from the parent, we will have to do two things. First say that we accept transclusion in our directive with the transclude option. The second thing is, we need to include the ngTransclude directive into our template to tell Angular, where to put the transcluded content. The above illustration can then look something like this:

app.directive('myDirective', function(){
return {
restrict: 'EA',
transclude: true,
scope: { title:'@' },
template: '<div style="border: 1px solid black;">' +
'<div style="background-color: gray">{{title}}</div>' +
'<ng-transclude></ng-transclude>' +
'</div>'
};
});

And in our HTML:

<my-directive title="{{title}}">{{text}}</my-directive>

See here a working plunker: http://plnkr.co/edit/b5HMkm

Directive with multi transclusion

With the new version 1.5 (current version is 1.4.7 while writing this post) Angular will introduce multi transclusion. Until now, you was able just to put the transclusion to one place, but in future you can do that more elegant with ng-transclusion slots.

So let’s modify our directive and html code. To make this work, we need to replace transclude: true with an object, similar to what we have for scope to look like this:

transclude: {
'dirTitle': 'titleSlot',
'dirBody': 'bodySlot'
}

The property is the element which we will use in our html and the value we will need to identify where to put the transclude. Then, we will need to modify our template to this:

template: '<div style="border: 1px solid black;">' +
'<div ng-transclude="titleSlot" style="background-color: gray"></div>' +
'<div ng-transclude="bodySlot"></div>' +
'</div>'

You see, that we have now two ng-transclude directives, but we gave them the name of the value in our transclude object. Now we can use our modified directive in HTML like this:

<my-directive-multi>
<dir-title>{{title}}</dir-title>
<dir-body><p>{{text}}</p></dir-body>
</my-directive-multi>

I put it into the same plunker as the example before: http://plnkr.co/edit/b5HMkm

You will notice in the HTML, that I have commented out the version 1.4.7. If you comment it in, you will recognize that it is not working.

<!--<script data-require="angular.js@1.4.x" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.7/angular.js" data-semver="1.4.7"></script>-->
<script src="https://code.angularjs.org/snapshot/angular.js"></script>

With this multi transclusion you are getting a much higher flexibility of creating your directive template. I personally like it :)

transclude option

From https://code.angularjs.org/1.1.5/docs/guide/directive

transclude — compile the content of the element and make it available to the directive. Typically used with ngTransclude. The advantage of transclusion is that the linking function receives a transclusion function which is pre-bound to the correct scope. In a typical setup the widget creates an isolate scope, but the transclusion is not a child, but a sibling of the isolate scope. This makes it possible for the widget to have private state, and the transclusion to be bound to the parent (pre-isolate) scope.
true — transclude the content of the directive.
‘element’ — transclude the whole element including any directives defined at lower priority.

transclude: true

<div>
<my-transclude-true>
<span>{{ something }}</span>
{{ otherThing }}
</my-transclude-true>
</div>

After compiling and before linking this becomes:

<div>
<my-transclude-true>
<!-- transcluded -->
</my-transclude-true>
</div>

The content (children) of my-transclude-true which is <span>{{ something }}</span> {{…, is transcluded and available to the directive.

After linking this can become the following if you do not do something different with the transcluded content:

<div>
<my-transclude-true>
<span>{{ something }}</span>
{{ otherThing }}
</my-transclude-true>
</div>

transclude: ‘element’

<div>
<my-transclude-element>
<span>{{ something }}</span>
{{ otherThing }}
</my-transclude-element>
</div>

After compiling and before linking this becomes:

<div>
<!-- transcluded -->
</div>

Here, the whole element including its children are transcluded and made available to the directive. After linking this can become the following if you do not do something different with the transcluded content:

<div>
<my-transclude-element>
<span>{{ something }}</span>
{{ otherThing }}
</my-transclude-element>
</div>

What happens after linking?

That’s up to your directive to do what it needs to do with the transclude function. ngRepeat uses transclude: ‘element’ so that it can repeat the whole element and its children when the scope changes. However, if you only need to replace the tag and want to retain its contents, you can use transclude: true with the ngTransclude directive which does this for you.

Further Reading

http://teropa.info/blog/2015/06/09/transclusion.html


Originally published at Damir Kusar.