I preciously asked on SO if it was possible to transclude the inner contents of a directive twice in the directive template (clone it and insert it in two places in the template).
A very helpful person helped me put this plunkr together.
http://plnkr.co/edit/k2UB1o4CTHtZ1voS0OKN?p=preview
It seems to work at first. The problem comes when I use any child element which uses transclusion itself. The error I get is...
[ngTransclude:orphan] Illegal use of ngTransclude directive in the template! No parent directive that requires a transclusion found. Element:
For example I have a button directive with the following definition.
angular.module('s4p.directives').directive('s4pButton', function () {
return {
restrict: 'E',
scope: {
icon: '@'
},
transclude: true,
replace: true,
template: getTemplate
};
function getTemplate(element, attr) {
var btnType = (typeof attr.type === 'undefined') ? 'button' : attr.type;
return '<button s4p-button type="' + btnType + '">'+
'<s4p-button-content ng-transclude></s4p-button-content>'+
'<s4p-button-icon ng-if="icon">'+
'<s4p-icon href="{{icon}}"></s4p-icon>'+
'</s4p-button-icon>'+
'</button>';
}
});
as soon as I put one of my buttons inside the tool bar and it tries to clone it I get the above error.
EDIT:
New PLUNKR with full example
http://plnkr.co/edit/uK8r4EA2IPRnYKfjWNVG?p=preview
Any help would be greatly appreciated.
The directive was trying to do the double transclusion in one invocation of the transclude function.
//PROBLEM Code
link: function(scope, element, attrs, controller, transclude) {
transclude(function(clone, scope) {
element.find('[transclude-main]').replaceWith(clone);
element.find('[transclude-overflow]').replaceWith($compile(clone.clone())(scope));
});
}
To transclude the directive contents into two places in the template, invoke the transclusion function twice.
app.directive('toolbar', function($compile) {
return {
restrict: 'E',
scope: {},
transclude: {
},
template:
'<toolbar-main><div transclude-main></div></toolbar-main>' +
'<toolbar-overflow><div transclude-overflow></div></toolbar-overflow>',
//CORRECTED code
link: function(scope, element, attrs, controller, transclude) {
transclude(scope, function(clone) {
element.find('[transclude-main]').replaceWith(clone);
});
transclude(scope, function(clone) {
element.find('[transclude-overflow]').replaceWith(clone);
});
}
};
});
If you need new scopes for the transclusion, you can create them with scope.$new()
.
var newScope = scope.$new();
transclude(newScope, function(clone) {
element.find('[transclude-main]').replaceWith(clone);
});
For more information on creating new scopes, see AngularJS $rootScope.scope API Reference -- $new.
AngularJS jqLite is a tiny, API-compatible subset of jQuery that allows Angular to manipulate the DOM in a cross-browser compatible way. jqLite implements only the most commonly needed functionality with the goal of having a very small footprint.1
The find
method of jqLite does not support attribute selectors. To make
the above example compatible with jqLite, use custom tags for the transclusion targets.
app.directive('toolbar', function($compile) {
return {
restrict: 'E',
scope: {},
transclude: {},
template:
'<toolbar-main><my-main></my-main></toolbar-main>' +
'<toolbar-overflow><my-overflow></my-overflow></toolbar-overflow>',
//CORRECTED code
link: function(scope, element, attrs, controller, transclude) {
transclude(scope, function(clone) {
element.find('my-main').replaceWith(clone);
});
transclude(scope, function(clone) {
element.find('my-overflow').replaceWith(clone);
});
}
};
});
This way there is no need to add jQuery to the app as a dependency.