AngularJS : transcluding multiple sub elements in a single Angular directive
Solution 1
Starting Angular 1.5, it's now possible to create multiple slots. Instead of transclude:true, you provide an object with the mappings of each slot:
https://docs.angularjs.org/api/ng/directive/ngTransclude
angular.module('multiSlotTranscludeExample', [])
.directive('pane', function(){
return {
restrict: 'E',
transclude: {
'title': '?pane-title',
'body': 'pane-body',
'footer': '?pane-footer'
},
template: '<div style="border: 1px solid black;">' +
'<div class="title" ng-transclude="title">Fallback Title</div>' +
'<div ng-transclude="body"></div>' +
'<div class="footer" ng-transclude="footer">Fallback Footer</div>' +
'</div>'
};
})
Solution 2
Cool question. I'm not sure there is a built in way, but you can do it yourself in a pretty generic way.
You can access the transcluded element by passing in the $transclude service like this:
$transclude(function(clone, $scope) {
Where clone will be a copy of the pre-linked transcluded content. Then if you label the content in the element like this:
<div id="content">
<div id="content0">{{text}}</div>
<div id="content1">{{title}}</div>
</div>
You can loop over the content and compile it like this:
$scope.transcludes.push($compile(clone[1].children[i])($scope));
Great! now you just need to put the content in the correct place in your template
'<div id="transclude0"></div>' +
'<div id="transclude1"></div>' +
Then you can in your link function assign the content correctly
angular.element(document.querySelector('#transclude' + i)).append(scope.transcludes[i]);
I set up a fiddle you can play with that has this set up.
Hope this helped!
Solution 3
In our project we have modeled multi site trasclusion after JSF 2's ui:composition, ui:insert, ui:define (see ui:composition).
Implementation consists of three simple directives: ui-template, ui-insert, ui-define (see angularjs-api/template/ui-lib.js).
To define a template one writes the following markup (see angularjs-api/template/my-page.html):
<table ui-template>
<tr>
<td ui-insert="menu"></td>
</tr>
<tr>
<td ui-insert="content"></td>
</tr>
</table>
and declares a directive (see angularjs-api/template/my-page.js):
var myPage =
{
templateUrl: "my-page.html",
transclude: true
};
angular.module("app").
directive("myPage", function() { return myPage; });
and finally, to instantiate the directive one needs to write (see angularjs-api/template/sample.html):
<my-page>
<div ui-define="content">
My content
</div>
<div ui-define="menu">
<a href="#file">File</a>
<a href="#edit">Edit</a>
<a href="#view">View</a>
</div>
</my-page>
The working sample can be seen through rawgit: sample.html
See also: Multisite Transclusion in AngularJS
Comments
-
epeleg over 3 years
I was reading about
ng-transclude
in the AngularJS docs on Creating a Directive that Wraps Other Elements and I think I understand properly what it does.If you have a directive that applies to an element that has content inside it such as the following:
<my-directive>directive content</my-directive>
it will allow you to tag an element within the directive's template with
ng-transclude
and the content included in the element would be rendered inside the tagged element.So if the template for
myDirective
is<div>before</div> <div ng-transclude></div> <div>after</div>
it would render as
<div>before</div> <div ng-transclude>directive content</div> <div>after</div>
My question is if it is possible to somehow pass more then a single block of html into my directive?
For example, suppose the directive usage would look like this:
<my-multipart-directive> <part1>content1</part1> <part2>content2</part2> </my-multipart-directive>
and have a template like:
<div> this: <div ng-transclude="part2"></div> was after that: <div ng-transclude="part1"></div> but now they are switched <div>
I want it to render as follows:
<div> this: <div ng-transclude="part2">content2</div> was after that: <div ng-transclude="part1">content1</div> but now they are switched <div>
Perhaps I could somehow bind the HTML value of a node to the model so that I will be able to use it in such a way without calling it "transclude"?
-
epeleg over 10 yearsthanks. this looks very close to what I was thinking of. I am not sure I understand what the $transclude service actually does but I guess I can read about it in the docs. why did you choose to have "div#content" as a wrapper of "div#content0" and "div#content1" and what would change if content was not there to wrap them? also what exactly is in the
clone
parameter? (what isclone[0]
ifclone[1]
isdiv#content
?) -
epeleg over 10 yearswell... I went to the docs and did not find any reference to $transclude ... where did you find it and how to use it?
-
hassassin over 10 yearsYou can see it in the transclude directive github.com/angular/angular.js/blob/… It is defined here: github.com/angular/angular.js/blob/… and described here: github.com/angular/angular.js/blob/…
-
epeleg over 10 yearsI would have expected it to show somewhere within docs.angularjs.org/api ... are you sure its o.k. to use? (being "undocumented") ?
-
hassassin over 10 yearsWell ultimately you need evaluate your situation. Is it possible to render what you want with normal transclusion? If you really have this complicated case, then it seems fine to use this. In some versions of angular you can get the transclude function as an argument to
$compile
. Either seem valid since normal transclusion would not be sufficient. -
epeleg over 10 yearsthanks for all the help and information. (at least for now) it is not an actual need, just something I was wandering about during my learning phase... maybe it would be even possible to create a directive that will wrap this up nicely... something like: multiTransclude="subjectTemplate,bodyTemplate" ...
-
Zach Snow almost 10 yearsI ended up needing similar functionality and wrote ng-multi-transclude. It allows you to do almost exactly what you requested: in the template you define named "holes" via
ng-multi-transclude
, and at include time you pass in multiple elements whosename
attributes match the holes you'd like them to fill. -
Jerry over 8 yearsFor anyone curious where this functionality is documented, search for $transclude in the $compile docs
-
epeleg over 8 yearsnice, They use my above suggested syntax almost as is. too bad that they did not update the docs for the transclude docs.angularjs.org/api/ng/service/$compile. I am also not sure I understand the naming convention of the the transclude object properties and their relation to the HTML elements of the directive's template and those in the HTML that uses the directive.
-
epeleg over 8 yearsit looks like the object is a mapping between the normalized transcluded elements tag names (i.e <pane-title> normalized to paneTitle) and the value of ng-transclude attribute of the directives template element .
-
epeleg over 8 yearsIMHO this is counter intuitive and I would have expected it to be the other way around in a similar fashion to how it works with scope:{} i.e. that the resolved value for the template would be on the left and value would give the instructions on where to get it from.
-
epeleg over 8 yearsi.e. I think the example would have been much clearer if it was
transclude: { 'title': '?paneTitle': , 'body':'paneBody' }
meaning that title for the template is optionaly resolved from thepane-title element
. Anybody from the angular team is reading this ?! -
epeleg over 8 yearsso I opened an issue for this (IMHO) required change of syntax here: github.com/angular/angular.js/issues/13439
-
epeleg over 8 yearsplease note that following the above issue the keys/values in the transclude object have been swapped. so it should now be transclude: { 'title':'?pane-title', 'body':'pane-body' }, I still asked for another change which is to use 'paneTitle' and 'paneBody' which are again more consistent with the scope object syntax. I hope they will accept this request as well.
-
John about 7 yearsin Angular 1.6.4 they are using the syntax:
paneTitle
instead ofpane-title
.