March 2, 2016
Angular 1.5 Multi Slot Transclusion
Angular 1.5 Multi Slot Transclusion
Transclusion is one of the most useful features when it comes to writing reusable Angular directives. Even if you never heard of what it is, you might have used transclusion before without knowing it. The version 1.5 of AngularJS adds a long awaited feature: Multi Slot Transclusion, that allows us developer to build ever more flexible API’s for our directives.
I’m not going through the whole concept of transclusion, there are a lot of posts in the internet (see Chapter ‘Further Reading’) that cover this topic.
Classic Transclusion in Angular 1.4 and below
typically you would use transclusion to let the user of your directive insert his custom HTML into your directive.
Given you have a markup such as the following:
1 2 3 4 5 |
<list> <list-item title="Football">is cool</list-item> <list-item title="Table Tennis">rocks</list-item> <list-item title="Football Table">is the best</list-item> </list> |
and two directive definitions like
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
app.directive('list', function() { return { restrict: 'AE', scope: {}, transclude: true, replace: true, template: '<ul ng-transclude></ul>' } }); app.directive('listItem', function() { return { restrict: 'AE', require: '^list', scope: { title: '@' }, transclude: true, replace: true, template:'<li></span><b>{{title}}: </b><span ng-transclude></span></span></li>' } }); |
The two directives list
and list-item
render the following snippet:
The Problem
Typically you start to run into limitations pretty soon. Lets imagine you want to create a directive similar to the example above:
- show the title of a
list-item
- display either a icon as a css-class or a
img
-tag on the left - show some textual
content
of course you could implement this by introducing one or two more directives and start nesting them inside each other, but in this case lets imagine we want to stick with only the two existing directives list
and list-item
The Solution
Multi Slot Transclusion is here to help deal with this kind of scenarios. With very little modifications to the directives created above we can easily add the desired behavior. IMHO the readability of the new directive we’re about to create is improved greatly compared to classic transclusion – especially from the point of view of a consumer of the directive.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<list> <list-item title="Football"> <icon> <img src="http://placehold.it/200x150"> </icon> <content>is cool</content> </list-item> <list-item title="Table Tennis"></list-item> <list-item title="Football Table"> <content>is the best</content> <icon class="glyphicon glyphicon-thumbs-up"></icon> </list-item> </list> |
As you can see we added a few more lines of code we’re about to cover now:
in line 2-6 we define a list-item
that still has an attribute title="Football"
as it has before. The additions we made start to appear inside the list-item
-tags where we added two more elements.
On the one hand we added a content
element that was the content in the first example that was transcluded in the list-item
directive. We now have a more semantical way of expressing what kind of HTML is transcluded here.
On the other hand there is a icon
element that shall represent our image/icon that we want to display on the left of the list. As we can see this element can have its own HTML elements inside the icon
-tags. In this example a placeholder image.
Whats important to note here is that the order of the elements inside the list-item
does not matter. What matters is the name of the elements inside it.
How does Angular know where to place the transcluded content you might wonder.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
app.directive('listItem', function() { return { restrict: 'AE', require: '^list', scope: { title: '@' }, transclude: { 'contentSlot': '?content', //a '?' indicates an optional slot 'iconSlot': '?icon' //a error gets thrown if a required slot is unavailable }, replace: true, template: '<li>' + '<span ng-transclude="iconSlot"></span>' + //iconSlot gets transcluded here '<span>'+ '<b>{{title}}: </b>' + '<span ng-transclude="contentSlot">This is a default</span>' + //content gets transcluded here '</span>' + '</li>' } }); |
Again we made minor adjustments to the directive from the first example. Mainly we redefined the transclude
-property of the directive definition. Where we previously just wrote transclude: true
we can now specify an object. The key (left part of the key-value) of the object is the name that is used internally in this directive to let angular know where we want the transcluded content to be inserted. you can see this in the template
definition of the directive. In line 15 we say
<span ng-transclude="iconSlot"></span>
and tell angular to insert the icon here.
same applies for the contentSlot, that gets inserted in line 18.
So the key of the transclude key-value is the internal name of the HTML-snippet. So whats the value then? We already saw what it is. The value is the name of the element in the template that uses our list-item
directive.
1 2 3 4 5 |
<list> <list-item title="Football Table"> <content>is the best</content> </list-item> </list> |
the content
-element is the name that is used in the directive’s transclusion definition: transclude: {'contentSlot': 'content'}
Thats all the magic that happens.
A few More thing to note are:
- classic transclusion still works the way it always did. An upgrade to Angular 1.5 wont break your application, but gives you the opportunity to use Multi Slot Transclusion.
- you can use ng-transclude-slot as an alias for ng-transclude
- you can use ng-transclude as an attribute and an element
- you can specify default transclusion content, take a look at the directive definition in our 2nd example
If you want to, you can take a look at this Plunkr and play a little with it, to get familiar with the newly introduces syntax.
Further Reading
- Transclusion explained
- Classic AngularJS Transclusion
- AngularJS 1.5 Multi Slot Transclusion
- AngularJS Docs about Transclusion
- Stackoverflow discussion about transclusion