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 combine 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.com/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 comments, 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 comment.
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..?
As the documentation states, 'replace' determines whether the current element is replaced by the directive. The other option is whether it is just added to as a child basically.
The ngRef attribute tells AngularJS to assign the controller of a component (or a directive) to the given property in the current scope. It is also possible to add the jqlite-wrapped DOM element to the scope. The ngRepeat directive instantiates a template once per item from a collection.
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.
It 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 template
The second step is to not define ng-transclude
directive in the template, instead use the transclude
function passed to link
for accessing the trancluded content of various slots, and replace the respective element with the transcluded content if it is present (using transclude.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($compile) {
return {
template: '<label class="fallback-label ng-transclude">Fallback </label>',
replace: true,
transclude: true
}
}).directive('inputTest', function($compile) {
return {
template: '<input type="text" class="fallback-input">',
replace: true
}
});
<script src="https://cdnjs.cloudflare.com/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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With