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
appendTois not specified, the modal will be appended to thebodyof your page and becomes a direct child of thebody. Therefore querying.contentin 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
$timeoutwrapper.
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