Last week I talked a little about using transclusion in AngularJS from a starting primer on the topic to where I still wasn't quite sure how to use it to pass DOM into a uibModal.
Here are a few more transclude-related links I rediscovered as I was closing out browser tabs this morning. With any luck, I'll come back and process this jive into something that answers the question I punted on for the time being last week.
From What exactly do you do with the transclude function and the clone linking function?:
As @Jonah pointed out below,ย here is a really good article on the compile option of directives and using the transclusion function
The basic idea is the compile function should return a linking function. You can use the transclusion function provided inside the linking function to take a clone of the transcluded DOM element, compile it, and insert it wherever it needs to be inserted.
Here is a better example I've [pull outta the air] on Plunker
The idea of the compile function is it gives you a chance to programmatically alter the DOM elements based on attributes passed BEFORE the linking function is created and called.
// a silly directive to repeat the items of a dictionary object.
app.directive('keyValueRepeat', function ($compile){
return {
transclude: true,
scope: {
data: '=',
showDebug: '@'
},
compile: function(elem, attrs, transclude) {
if(attrs.showDebug) {
elem.append('<div class="debug">DEBUG ENABLED {{showDebug}}</div>');
}
return function(scope, lElem, lAttrs) {
var items = [];
console.log(lElem);
scope.$watch('data', function(data) {
// remove old values from the tracking array
// (see below)
for(var i = items.length; i-- > 0;) {
items[i].element.remove();
items[i].scope.$destroy();
items.splice(i,1);
}
//add new ones
for(var key in data) {
var val = data[key],
childScope = scope.$new(),
childElement = angular.element('<div></div>');
// for each item in our repeater, we're going to create it's
// own scope and set the key and value properties on it.
childScope.key = key;
childScope.value = val;
// do the transclusion.
transclude(childScope, function(clone, innerScope) {
//clone is a copy of the transcluded DOM element content.
console.log(clone);
// Because we're still inside the compile function of the directive,
// we can alter the contents of each output item
// based on an attribute passed.
if(attrs.showDebug) {
clone.prepend('<span class="debug">{{key}}: {{value}}</span>');
}
//append the transcluded element.
childElement.append($compile(clone)(innerScope));
});
// add the objects made to a tracking array.
// so we can remove them later when we need to update.
items.push({
element: childElement,
scope: childScope
});
lElem.append(childElement);
}
});
};
}
};
});
So that's at least a detailed example of using the transclude function in a compile
.
You should probably also know the difference between transclude: true
and transclude: 'element'
.
This one I need to read again to understand what's going on precisely, but seems interesting.
How to make transcluded template to bind to component scope:
You can programmatically transclude using the $transclude service and append the elements to the form like this:
$transclude($scope, function(clone) {
$element.find('form').append(clone);
})
Then you add the elements to your form controller like this:
$scope.testForm.$addControl($element);
And then to follow back up on Transclusion in Angular UI Modal not working, here's a bit of the answer given:
Taking the above points into account, theย openModal
ย method in the link function of your custom directiveย theModal
ย should look like the following:
scope.control.openModal = function () {
scope.instance = $uibModal.open({
animation: false,
scope: scope,
template: '<div>in the template</div><div class="content"></div>',
appendTo: element
});
/**
* Give Angular some time to render your DOM
*/
$timeout(function (){
/**
* Since you have jQuery loaded already
*/
var content = element.find('.content');
/**
* Finally, append the transcluded element to the correct position,
* while also making sure that the cloned DOM is bound to the parent scope (i.e. ctl)
*/
transclude(scope.$parent, function(clonedContent){
content.append(clonedContent);
});
});
};
Note how theย transclude
ย function gives you control over how you want to bind some transcluded DOM toย a custom scopeย and NOT the default directive's scope. The plainย transclude()
ย call will take the current available scope object into account - i.e. the directive's scope - for binding the transcluded DOM.
So, again, I punted and created a reusable modal that expects a specific payload in scope that gets mapped to the body. Here's just the body of the modal to demonstrate (no comments in the live version, natch):
<div id="multiMessageModal" class="modal-body">
<span ng-repeat="message in messages">
<!-- If you got text, put it here -->
<p ng-if="message.text">{{message.text}}</p>
<!-- If you got list items that may be related to text (but can be their own thing), put them here -->
<ul ng-if="isArray(message.listItems) && message.listItems.length">
<li ng-repeat="listitem in message.listItems">{{listitem}}</li>
</ul>
<!-- And then note that we're repeating as often as you want
so you can have as many texts or lists in a row by leaving
the other part falsy with each `message` -->
</span>
</div>
Makes sense, right? So if you need text, list items, then text, you have two messages
, one with text
and listItems
and the next with just text
. You could do three different unordered lists in a row with text before and after, or just three paragraphs of text with [{ text: 'p1' }, { text: 'p2' }, { text: 'p3' }]
, whatever.
Again, not nearly as good as being able to pass any template at all in a call to the modal, but it's doing the job for now. I still feel a little dirty from the hack, though.
Labels: angularjs, noteToSelf