The objective of this plunk is to transclude elements into an Angular UI Modal from a controller, where the Modal is wrapped by a directive. The solution should follow these premises:
input1
variable in the controller that should set a value in the Modal).content
element to transclude the fields. This element is in the modal's template. I'm not sure when this template is available to transclude it.To summarize, the objective is to have a set of fields declared in the controller HTML markup and available in the modal, where the modal is wrapped in a directive and the scope is managed in the controller. Any ideas will be greatly appreciated.
HTML
<div the-modal control="modalCtl">
<p>some text</p>
<input type="text" ng-model="input1" />
</div>
<button type="button" ng-click="open()">Open me!</button>
Javascript
var app = angular.module("app", ['ui.bootstrap']);
app.controller("ctl", function($scope,$timeout) {
$scope.modalCtl = {};
$scope.input1 = "abc";
$scope.open = function(){
$scope.modalCtl.openModal();
};
});
app.directive("theModal", function($uibModal) {
return {
restrict: "AE",
scope: {
control: "="
},
transclude: true,
link: function (scope, element, attrs, ctrl, transclude) {
scope.control = scope.control || {}
scope.control.openModal = function () {
scope.instance = $uibModal.open({
animation: false,
scope: scope,
template: '<div>in the template</div><div class="content"></div>'
});
element.find('.content').append(transclude());
};
}
}
});
You have come close enough to achieving your objective with transclusion but, there are a few things you need to consider:
First of all, according to UI Bootstrap docs, there is an appendTo
property in the options
for the $uibModal.open()
method which defaults to body
.
If
appendTo
is not specified, the modal will be appended to thebody
of your page and becomes a direct child of thebody
. Therefore querying.content
in your directive viaelement.find('.content')
won't work because it doesn't exist there.
Secondly, AngularJS comes with jQLite, a lightweight version of jQuery. This implies that there is limited support for most of jQuery's functionalities. One such case is with the .find()
method which only works with tag names.
To make it work how it does with jQuery (although you don't really have to because you could still use .children()
in a chain to query nested DOM elements), you'll have to load jQuery before Angular (which I suppose you have already).
Refer AngularJS docs on angular.element
for more info.
Rendering DOM takes a little bit of time for Angular since it needs to make the correct bindings related to scopes and the views, to complete a digest cycle, and so on. Therefore you may end up instantly querying a DOM element which in fact might not have been rendered yet.
The trick to wait for DOM rendering and completion of a digest cycle is to wrap your DOM related code into
$timeout
wrapper.
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>',
/**
* Make sure the modal is appended to your directive and NOT `body`
*/
appendTo: element
});
/**
* Give Angular some time to render your DOM
*/
$timeout(function (){
/**
* In case jQuery is not available
*/
// var content = element.children('.modal').children('.modal-dialog').children('.modal-content').children('.content');
/**
* 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.
As the previous answers suggest, you can use the appendTo
property to provide the element of your directive as the parent of the modal.
You can "wait for the modal's template to be rendered" by using the rendered
promise in the UibModalIstance. (Documentation).
scope.control.openModal = function () {
scope.instance = $uibModal.open({
animation: false,
scope: scope,
template: '<div>in the template</div><div class="content"></div>',
appendTo: element
});
// We use the redered promise to make sure
// the modal's template has been loaded.
scope.instance.rendered.then(function (){
// You'll most likely want to pass the `$parent` scope as the first
// parameter for proper scope binding with your controller.
element.find('.content').append(transclude(scope.$parent));
});
};
Here's the modified plunker.
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