I'm trying to do the following:
If I add <my-custom-directive></<my-custom-directive>
it should expand to
<div class="my-custom-container">
<label class="my-custom-label">Fallback</label>
<input class="my-custom-input"/>
</div>
which can be done by setting the above as template
and replace:true
in DDO.
If I add the following in HTML:
<my-custom-directive>
<my-custom-label class="users-custom-class"><span>Custom content</span><my-custom-label>
</<my-custom-directive>
it should expand to
<div class="my-custom-container">
<label class="my-custom-label users-custom-class"><span>Custom content</span></label>
<input class="my-custom-input"/>
</div>
Which means if the user wants to provide custom <label>
, <input>
etc, we use transclusion, and the transcluded content replaces respective slot in the original template, similar to how a replace:true
directives's would replace itself with it's template.
I'm not able to bine the replace
and transclusion functionality.
What I've so far (something-working-state) is the following:
angular.module('test', [])
.directive('transTest', function() {
return {
transclude: {
lab: '?labelTest',
inp: '?inputTest'
},
replace: true,
template: '<div class="container"><label ng-transclude="lab">Fallbacl label</label><input type="text" placeholder="fallback" ng-transclude="inp"></div>',
link: function(scope, element, attrs, ctrl, transclude) {
console.log(transclude())
}
}
});
<script src=".js/1.5.2/angular.js"></script>
<div ng-app="test">
<div trans-test class="test">
<label-test>test label</label-test>
<input-test>test input</input-test>
</div>
</div>
I'm trying to do the following:
If I add <my-custom-directive></<my-custom-directive>
it should expand to
<div class="my-custom-container">
<label class="my-custom-label">Fallback</label>
<input class="my-custom-input"/>
</div>
which can be done by setting the above as template
and replace:true
in DDO.
If I add the following in HTML:
<my-custom-directive>
<my-custom-label class="users-custom-class"><span>Custom content</span><my-custom-label>
</<my-custom-directive>
it should expand to
<div class="my-custom-container">
<label class="my-custom-label users-custom-class"><span>Custom content</span></label>
<input class="my-custom-input"/>
</div>
Which means if the user wants to provide custom <label>
, <input>
etc, we use transclusion, and the transcluded content replaces respective slot in the original template, similar to how a replace:true
directives's would replace itself with it's template.
I'm not able to bine the replace
and transclusion functionality.
What I've so far (something-working-state) is the following:
angular.module('test', [])
.directive('transTest', function() {
return {
transclude: {
lab: '?labelTest',
inp: '?inputTest'
},
replace: true,
template: '<div class="container"><label ng-transclude="lab">Fallbacl label</label><input type="text" placeholder="fallback" ng-transclude="inp"></div>',
link: function(scope, element, attrs, ctrl, transclude) {
console.log(transclude())
}
}
});
<script src="https://cdnjs.cloudflare./ajax/libs/angular.js/1.5.2/angular.js"></script>
<div ng-app="test">
<div trans-test class="test">
<label-test>test label</label-test>
<input-test>test input</input-test>
</div>
</div>
As you can see, the trancluded content goes inside the translude containing element, instead of replacing it. I've read the source code ments, articles and also checked the implementation of ui-bootstrap-accordion and tried my luck with transclude:'element'
, but it leaves nothing in DOM but a ment.
transclusion, replace etc are the available options that I've found which offers functionality similar to what I'm trying to achieve. But they don't seem to play well toghether. What is the correct way to achieve this kind of functionality in angular, if possible..?
Share Improve this question edited Mar 30, 2016 at 4:23 T J asked Mar 25, 2016 at 19:14 T JT J 43.2k13 gold badges86 silver badges142 bronze badges 2-
1
It seems muli-slot
element
transclude not yet been supported. There has #14244 talking similar behavior. – Cotton Commented Mar 30, 2016 at 2:18 - @Cotton that looks like the functionality I'm after... thanks for the pointer – T J Commented Mar 30, 2016 at 3:29
2 Answers
Reset to default 5It seems I finally made it work. The solution is made of 2 parts:
Define directives for the transclude-slot elements with template similar to the fallback (default).
The major reason for doing this is to make use of angular's built in capability to copy attributes to templates root element when
replace:true
is set in DDO. I didn't wanted to do it manually in the link function.
Another reason is that it lets you add additional features such as transclusion which is not necessary in default templateThe second step is to not define
ng-transclude
directive in the template, instead use thetransclude
function passed tolink
for accessing the trancluded content of various slots, and replace the respective element with the transcluded content if it is present (usingtransclude.isSlotFilled()
)
Well, this wasn't easy to get my mind around, and it isn't easy to explain as well. Hope the demo below explains it better than words:
angular.module('test', [])
.directive('transTest', function() {
return {
replace: true,
transclude: {
lab: '?labelTest',
inp: '?inputTest'
},
template: '<div class="test-parent"><label class="fallback-label">Fallback </label><br><input type="text" class="fallback-input"></div>',
link: function(scope, element, attrs, ctrl, transclude) {
if (transclude.isSlotFilled('lab')) {
var label = transclude(angular.noop, null, 'lab');
element.find('label').replaceWith(label);
}
if (transclude.isSlotFilled('inp')) {
var input = transclude(angular.noop, null, 'inp');
element.find('input').replaceWith(input);
}
}
}
}).directive('labelTest', function($pile) {
return {
template: '<label class="fallback-label ng-transclude">Fallback </label>',
replace: true,
transclude: true
}
}).directive('inputTest', function($pile) {
return {
template: '<input type="text" class="fallback-input">',
replace: true
}
});
<script src="https://cdnjs.cloudflare./ajax/libs/angular.js/1.5.2/angular.js"></script>
<div ng-app="test">
<div trans-test class="test">
<label-test class="custom-label">Custom content</label-test>
<input-test class="custom-input" placeholder="custom"></input-test>
</div>
<br>
<br>
<div trans-test class="test">
</div>
</div>
You might be looking for a more elegant solution, but you can tap into the directive's controller $transclude
function to find out if a transclusion-slot
has been filled.
controller: function($transclude, $scope) {
$scope.fallback = !$transclude.isSlotFilled('lab');
Then, use that information to build your template.
template: '<div class="container">\
<label ng-if="fallback === true">Fallback label</label>\
<div ng-if="fallback === false" ng-transclude="lab"></div>\
However, if you can fully define the content being transcluded,
<label-test>
<label>test label</label>
</label-test>
it might make more sense for the destination content to be replaced - rather than the entire element:
template: '<div class="container">\
<div ng-transclude="lab">\
<label>Fallback label</label>\
</div>\
Here is a plunker showing both approaches: http://plnkr.co/edit/EEq5vovFrSW7kG81yWuf?p=preview