Why does $modal introduce an extra scope for the form? - angularjs

I've managed to use the example from angular-ui-bootstrap to demonstrate the problem (but you need the console open to see it) Plnkr link to this code
Why is the form being stuck in a sub scope instead of this $scope?
The HTML
<!doctype html>
<html ng-app="plunker">
<head>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.2.8/angular.js"></script>
<script src="http://angular-ui.github.io/bootstrap/ui-bootstrap-tpls-0.10.0.js"></script>
<script src="example.js"></script>
<link href="//netdna.bootstrapcdn.com/bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div ng-controller="ModalDemoCtrl">
<script type="text/ng-template" id="myModalContent.html">
<div class="modal-header">
<h3>I'm a modal!</h3>
</div>
<div class="modal-body">
<ng-form name="myForm">
<ul>
<li ng-repeat="item in items">
<a ng-click="selected.item = item">{{ item }}</a>
</li>
</ul>
Selected: <b>{{ selected.item }}</b>
</ng-form>
</div>
<div class="modal-footer">
<button class="btn btn-primary" ng-click="ok()">OK</button>
<button class="btn btn-warning" ng-click="cancel()">Cancel</button>
</div>
</script>
<button class="btn btn-default" ng-click="open()">Open me!</button>
<div ng-show="selected">Selection from a modal: {{ selected }}</div>
</div>
</body>
</html>
** The Javascript **
angular.module('plunker', ['ui.bootstrap']);
var ModalDemoCtrl = function ($scope, $modal, $log) {
$scope.items = ['item1', 'item2', 'item3'];
$scope.open = function () {
var modalInstance = $modal.open({
templateUrl: 'myModalContent.html',
controller: ModalInstanceCtrl,
resolve: {
items: function () {
return $scope.items;
}
}
});
modalInstance.result.then(function (selectedItem) {
$scope.selected = selectedItem;
}, function () {
$log.info('Modal dismissed at: ' + new Date());
});
};
};
// Please note that $modalInstance represents a modal window (instance) dependency.
// It is not the same as the $modal service used above.
var ModalInstanceCtrl = function ($scope, $modalInstance, items) {
$scope.items = items;
$scope.selected = {
item: $scope.items[0]
};
$scope.ok = function () {
console.log("The form in the $scope says: ", $scope.myForm);
console.log("The form in the $scope.$$childTail says: ", $scope.$$childTail.myForm);
$modalInstance.close($scope.selected.item);
};
$scope.cancel = function () {
$modalInstance.dismiss('cancel');
};
};
The ng-form is not in the $scope where I would expect it to be, even though the other $scope items are there. What is the cause of the nested scope for the form (that is added by angular for form validation).
The output from the console looks like:
The form in the $scope says: undefined example.js:37
The form in the $scope.$$childTail says:
Constructor {$error: Object, $name: "myForm", $dirty: false, $pristine: true, $valid: true…}

If you do not specify a scope property on $modal.open it take $rootscope. This is from documentation
scope - a scope instance to be used for the modal's content (actually
the $modal service is going to create a child scope of a provided
scope). Defaults to $rootScope
so set scope before call
var modalInstance = $modal.open({
templateUrl: 'myModalContent.html',
controller: ModalInstanceCtrl,
scope:$scope,
resolve: {
items: function () {
return $scope.items;
}
}
});

If you want to access the $scope declared on your main controller and make it accessible to the modal you have to include in your modal configuration.
scope: $scope
Just like what Chandermani put up on his answer above.

This is because transclusion used in modals which creates new scope for form. I am defining forms this way:
<form name="$parent.myForm">
Then you can use $scope.myForm in modal controller.

Related

Bootstrap dialog in Angular js

I am new to Angular JS. I started learning Angular JS today. Before I was using jQuery and Bootstrap for front-end. Now I want to show Bootstrap dialog box using AngularJS. I found this, https://angular-ui.github.io/bootstrap/.
But there are so many things difficult for beginners to understand. How can I correct my code to show bootstrap dialog?
This is my code
<html>
<head>
<title>Angular</title>
<link rel="stylesheet" href="http://localhost:8888/angular/bootstrap.css"></link>
<script src="http://localhost:8888/angular/angular-js.min.js"></script>
<script src="http://localhost:8888/angular/angular-js.animate.min.js"></script>
<script src="http://localhost:8888/angular/angular-js.sanitize.min.js"></script>
<script src="http://localhost:8888/angular/ui-bootstrap-tpls.min.js"></script>
</head>
<body>
<div ng-controller="ModalDemoCtrl as $ctrl">
<script type="text/ng-template" id="myModalContent.html">
<div class="modal-header">
<h3 class="modal-title" id="modal-title">I'm a modal!</h3>
</div>
<div class="modal-body" id="modal-body">
<ul>
<li ng-repeat="item in $ctrl.items">
{{ item }}
</li>
</ul>
Selected: <b>{{ $ctrl.selected.item }}</b>
</div>
<div class="modal-footer">
<button class="btn btn-primary" type="button" ng-click="$ctrl.ok()">OK</button>
<button class="btn btn-warning" type="button" ng-click="$ctrl.cancel()">Cancel</button>
</div>
</script>
<button type="button" class="btn btn-default" ng-click="$ctrl.open()">Open me!</button>
<button type="button" class="btn btn-default" ng-click="$ctrl.open('lg')">Large modal</button>
<button type="button" class="btn btn-default" ng-click="$ctrl.open('sm')">Small modal</button>
<button type="button" class="btn btn-default" ng-click="$ctrl.toggleAnimation()">Toggle Animation ({{ $ctrl.animationsEnabled }})</button>
<button type="button" class="btn btn-default" ng-click="$ctrl.openComponentModal()">Open a component modal!</button>
<div ng-show="$ctrl.selected">Selection from a modal: {{ $ctrl.selected }}</div>
</div>
</body>
<script>
angular.module('ui.bootstrap.demo').controller('ModalDemoCtrl', function ($uibModal, $log) {
var $ctrl = this;
$ctrl.items = ['item1', 'item2', 'item3'];
$ctrl.animationsEnabled = true;
$ctrl.open = function (size) {
var modalInstance = $uibModal.open({
animation: $ctrl.animationsEnabled,
ariaLabelledBy: 'modal-title',
ariaDescribedBy: 'modal-body',
templateUrl: 'myModalContent.html',
controller: 'ModalInstanceCtrl',
controllerAs: '$ctrl',
size: size,
resolve: {
items: function () {
return $ctrl.items;
}
}
});
modalInstance.result.then(function (selectedItem) {
$ctrl.selected = selectedItem;
}, function () {
$log.info('Modal dismissed at: ' + new Date());
});
};
$ctrl.openComponentModal = function () {
var modalInstance = $uibModal.open({
animation: $ctrl.animationsEnabled,
component: 'modalComponent',
resolve: {
items: function () {
return $ctrl.items;
}
}
});
modalInstance.result.then(function (selectedItem) {
$ctrl.selected = selectedItem;
}, function () {
$log.info('modal-component dismissed at: ' + new Date());
});
};
$ctrl.toggleAnimation = function () {
$ctrl.animationsEnabled = !$ctrl.animationsEnabled;
};
});
// Please note that $uibModalInstance represents a modal window (instance) dependency.
// It is not the same as the $uibModal service used above.
angular.module('ui.bootstrap.demo').controller('ModalInstanceCtrl', function ($uibModalInstance, items) {
var $ctrl = this;
$ctrl.items = items;
$ctrl.selected = {
item: $ctrl.items[0]
};
$ctrl.ok = function () {
$uibModalInstance.close($ctrl.selected.item);
};
$ctrl.cancel = function () {
$uibModalInstance.dismiss('cancel');
};
});
// Please note that the close and dismiss bindings are from $uibModalInstance.
angular.module('ui.bootstrap.demo').component('modalComponent', {
templateUrl: 'myModalContent.html',
bindings: {
resolve: '<',
close: '&',
dismiss: '&'
},
controller: function () {
var $ctrl = this;
$ctrl.$onInit = function () {
$ctrl.items = $ctrl.resolve.items;
$ctrl.selected = {
item: $ctrl.items[0]
};
};
$ctrl.ok = function () {
$ctrl.close({$value: $ctrl.selected.item});
};
$ctrl.cancel = function () {
$ctrl.dismiss({$value: 'cancel'});
};
}
});
</script>
</html>
But my code is not working. There are too many options I do not know in this for example "myModalContent.html" is from where? Would you explain step by step to show Bootstrap dialog box in AngularJS?
I tried this way as well
<html>
<head>
<title>Angular</title>
<script src="angular-js.min.js"></script>
<script src="angular-js.animate.min.js"></script>
<script src="angular-js.sanitize.min.js"></script>
<script src="angular-js.touch.min.js"></script>
<script src="bootstrap.ui.js"></script>
<link href="bootstrap.css" rel="stylesheet">
</head>
<body ng-app="MyApp" ng-controller="MyCtrl">
<button class="btn" ng-click="open()">Open Modal</button>
<div modal="showModal" close="cancel()">
<div class="modal-header">
<h4>Modal Dialog</h4>
</div>
<div class="modal-body">
<p>Example paragraph with some text.</p>
</div>
</div>
</body>
<script>
var app = angular.module("MyApp",["ui.bootstrap.modal"]);
app.controller('MyCtrl',function($scope){
$scope.open = function() {
$scope.showModal = true;
};
})
</script>
</html>
It shows like this:
It is not working as well. How can I show bootstrap dialog in AngularJS?
You don't have a ctrl.open function although you ref one in your ng-click.
Maybe you can try this?
$ctrl.open = function (size) {
var modalInstance = $uibModal.open({
animation: $ctrl.animationsEnabled,
ariaLabelledBy: 'modal-title',
ariaDescribedBy: 'modal-body',
templateUrl: 'myModalContent.html',
controller: 'ModalInstanceCtrl',
controllerAs: '$ctrl',
size: size,
resolve: {
items: function () {
return $ctrl.items;
}
}
});
open() method returns a modal instance. This is what actually creates the modal on your template.
templateurl is a path to your template. Here, its looking for myModalContent.html in the same path as your controller code. You need to create and design the template and that will get loaded when this modal is called in your view.
template can be used here instead of templateurl for inline HTML if you do not have enough HTML. "enough" is subjective to your understanding.
controller is the name of your controller. You can define it as you like.
controllerAs is the alias of your controller. You can define it to be what you like too. $ctrl is the alias of your controller by default. If you remove the controllerAs option from here, you can still use $ctrl in your template. Although, using this requires you to have controller option provided.
animationsenabled is a boolean value which can be used to toggle modal animation in your view by toggling the animation property. Default is false.
resolve helps create a $resolve object on the opened modal. This helps provide all resolved values from your items. Its basically a configuration on the $routeprovider service of Angular. To help understand $resolve better, you can have a look at this
Since you are new to Angular, much like me, a great place to start would be to understand Services in Angular. Its going to be one of your most used feature.
Moreover, you can check out plunker of the above code, edit it and understand the functioning. I tried it yesterday for the first time too.
Also, add angular-ui as a tag to your question so the angular-ui guys can get involved.
Hope this helps in some way.

Unable to make angular-formly work within a ng-template of an angular-ui-bootstrap modal

I am using angular-formly to build a form inside an angular-ui-bootstrap modal, the following code works when the form is placed outside the modal template but it doesn't when placed inside the ng-template, it just doesn't print the fields at all.
I believe this should work but I don't know how the life-cycle of angular-formly runs, so I am unable to identify how to make the fields show up inside my bootstrap modal template.
The issue is clearly related to the ng-template, it appears not to render the form even if the fields array is passed correctly.
var app = angular.module("demo", ['dndLists', 'ui.bootstrap', 'formly', 'formlyBootstrap']);
app.controller('ModalInstanceCtrl', function ($scope, $uibModalInstance, items, User) {
var vm = this;
vm.loadingData = User.getUserData().then(function(result) {
vm.model = result[0];
vm.fields = result[1];
vm.originalFields = angular.copy(vm.fields);
console.log(vm);
});
});
app.controller("AdvancedDemoController", function($scope, $uibModal){
$scope.modalOpen = function(event, index, item){
var modalInstance = $uibModal.open({
animation: true,
templateUrl: 'myModalContent.html',
controller: 'ModalInstanceCtrl',
size: 'md',
resolve: {
items: function () {
return $scope.items;
}
}
});
modalInstance.result.then(function (selectedItem) {
$scope.selected = selectedItem;
}, function () {
$log.info('Modal dismissed at: ' + new Date());
});
};
});
In my view:
<!-- Template for a modal -->
<script type="text/ng-template" id="myModalContent.html">
<div class="modal-header">
<h3 class="modal-title">I'm a modal!</h3>
</div>
<div class="modal-body">
<div ng-if="vm.loadingData.$$state.status === 0" style="margin:20px 0;font-size:2em">
<strong>Loading...</strong>
</div>
<div ng-if="vm.loadingData.$$status.state !== 0">
<form ng-submit="vm.onSubmit()" novalidate>
<formly-form model="vm.model" fields="vm.fields" form="vm.form">
<button type="submit" class="btn btn-primary submit-button">Submit</button>
</formly-form>
</form>
</div>
<ul>
<li ng-repeat="item in items">
{{ item }}
</li>
</ul>
Selected: <b>{{ selected.item }}</b>
</div>
</script>
Any ideas?
When calling $uibModal.open you need to specify controllerAs: 'vm' (that's what you're template assumes the controller is defined as).

Angular bootstrap ui modal scope binding with parent

I am having an Angular issue getting a modal scope to unbind from the parent scope. I want the scope object I pass into the modal to be separate from the corresponding scope object.
No matter how I structure the modal object the parent always mirrors it. The only solution I have found is to change the object property names, but that would be cumbersome to do for every modal in my project.
For example, I can have a $scope variable in the parent $scope.parentData.firstName and a modal variable $scope.modalData.a.b.c.firstName, and the parent will mirror the modal value.
I guess there is some parent child $scope issues here that I am not getting.
Here is a plunk illustrating the issue:
http://plnkr.co/edit/5naWXfkv7kmzFp7U2KPv?p=preview
HTML:
<div ng-controller="ModalDemoCtrl">
<script type="text/ng-template" id="myModalContent.html">
<div class="modal-header">
<h3>I'm a modal!</h3>
</div>
<div class="modal-body">
<input ng-model="modalData.a" />
<input ng-model="modalData.b" />
<input ng-model="modalData.c" />
Selected: <b>{{ sourceData }}</b>
</div>
<div class="modal-footer">
<button class="btn btn-primary" ng-click="ok()">OK</button>
<button class="btn btn-warning" ng-click="cancel()">Cancel</button>
</div>
</script>
<button class="btn btn-default" ng-click="open()">Open me!</button>
{{sourceData}}
<div ng-show="sourceData">Selection from a modal: {{ test }}</div>
</div>
</body>
</html>
JS:
angular.module('plunker', ['ui.bootstrap']);
var ModalDemoCtrl = function ($scope, $modal, $log) {
$scope.sourceData = {a:'abc',b:'bcd',c:'cde'};
$scope.open = function () {
var modalInstance = $modal.open({
templateUrl: 'myModalContent.html',
controller: ModalInstanceCtrl,
resolve: {
data: function () {
return $scope.sourceData;
}
}
});
modalInstance.result.then(function (selectedItem) {
$scope.scopeData = selectedItem;
}, function () {
$log.info('Modal dismissed at: ' + new Date());
});
};
};
// Please note that $modalInstance represents a modal window (instance) dependency.
// It is not the same as the $modal service used above.
var ModalInstanceCtrl = function ($scope, $modalInstance, data) {
$scope.modalData = {};
$scope.modalData = data;
$scope.ok = function () {
$modalInstance.close($scope.modalData);
};
$scope.cancel = function () {
$modalInstance.dismiss('cancel');
};
};
You are passing a reference to your current object, what you want to do is use angular.copy() to pass a deep copy of the object to the modal plnkr:
var modalInstance = $modal.open({
templateUrl: 'myModalContent.html',
controller: ModalInstanceCtrl,
resolve: {
data: function () {
return angular.copy($scope.sourceData); // deep copy
}
}
});

AngularJS UI Bootstrap Scope not disposed after modal dismiss

plunker: http://plnkr.co/edit/CrvOFHSfGnXFFWbaXNxn?p=preview
It seems scope for modal is not disposed of after closing the dialog. I have directive that emits when certain div is available, and modal controller receives it. It works fine on first open and close, one emit one receive. On 2nd dialog open and close, one emit and two receives, showing that there are two controller instances, and it goes on for any subsequent dialog open and close.
Is there anyway to make sure to dispose controller scope after modal dialog is dismissed?
html:
<!doctype html>
<html ng-app="ui.bootstrap.demo">
<head>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.16/angular.js"></script>
<script src="//angular-ui.github.io/bootstrap/ui-bootstrap-tpls-0.11.2.js"></script>
<script src="example.js"></script>
<link href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div ng-controller="ModalDemoCtrl">
<script type="text/ng-template" id="myModalContent.html">
<div class="modal-header">
<h3 class="modal-title">I'm a modal!</h3>
</div>
<div class="modal-body" my-hook>
<ul>
<li ng-repeat="item in items">
<a ng-click="selected.item = item">{{ item }}</a>
</li>
</ul>
Selected: <b>{{ selected.item }}</b>
</div>
<div class="modal-footer">
<button class="btn btn-primary" ng-click="ok()">OK</button>
<button class="btn btn-warning" ng-click="cancel()">Cancel</button>
</div>
</script>
<button class="btn btn-default" ng-click="open()">Open me!</button>
<button class="btn btn-default" ng-click="open('lg')">Large modal</button>
<button class="btn btn-default" ng-click="open('sm')">Small modal</button>
<div ng-show="selected">Selection from a modal: {{ selected }}</div>
</div>
</body>
</html>
js:
angular.module('ui.bootstrap.demo', ['ui.bootstrap']);
angular.module('ui.bootstrap.demo').controller('ModalDemoCtrl', function ($scope, $modal, $log) {
$scope.items = ['item1', 'item2', 'item3'];
$scope.open = function (size) {
var modalInstance = $modal.open({
templateUrl: 'myModalContent.html',
controller: 'ModalInstanceCtrl',
size: size,
resolve: {
items: function () {
return $scope.items;
}
}
});
modalInstance.result.then(function (selectedItem) {
$scope.selected = selectedItem;
}, function () {
$log.info('Modal dismissed at: ' + new Date());
});
};
});
// Please note that $modalInstance represents a modal window (instance) dependency.
// It is not the same as the $modal service used above.
angular.module('ui.bootstrap.demo').controller('ModalInstanceCtrl', function ($scope, $modalInstance, $rootScope, items) {
$scope.items = items;
$scope.selected = {
item: $scope.items[0]
};
$scope.ok = function () {
$modalInstance.close($scope.selected.item);
};
$scope.cancel = function () {
$modalInstance.dismiss('cancel');
};
$rootScope.$on ('MyTestHook', function(event) {
console.log("MyTestHook received");
});
});
angular.module('ui.bootstrap.demo').directive('myHook', function($timeout,$rootScope) {
function link(scope,element,attrs) {
$timeout(function(){
scope.$root.$emit("MyTestHook");
},500);
}
return {
link: link
};
});
This is not the problem of Modal. Modal scope is destroyed completely on dismiss.
Problem is in the code as you have attached $on on $rootScope so every time you click on model it add new listener in $$listeners and listener's count increased by one because $rootscope doesn't get disposed.
$rootScope.$on ('MyTestHook', function(event) { //every call to open modal will add new listener in rootscope
console.log("MyTestHook received");
});
To avoid this use attach $on on scope that disposed on modal dismiss.
See below debugger image.
http://plnkr.co/edit/iR7rGCFgIchsk4sBg26Q?p=preview

Scope issue in AngularJS using AngularUI Bootstrap Modal

plunker: http://plnkr.co/edit/wURNg8ByPYbEuQSL4xwg
example.js:
angular.module('plunker', ['ui.bootstrap']);
var ModalDemoCtrl = function ($scope, $modal) {
$scope.open = function () {
var modalInstance = $modal.open({
templateUrl: 'modal.html',
controller: 'ModalInstanceCtrl'
});
};
};
var ModalInstanceCtrl = function ($scope, $modalInstance) {
$scope.ok = function () {
alert($scope.text);
};
$scope.cancel = function () {
$modalInstance.dismiss('cancel');
};
};
index.html:
<!doctype html>
<html ng-app="plunker">
<head>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.0.8/angular.js"></script>
<script src="http://angular-ui.github.io/bootstrap/ui-bootstrap-tpls-0.6.0.js"></script>
<script src="example.js"></script>
<link href="//netdna.bootstrapcdn.com/twitter-bootstrap/2.3.1/css/bootstrap-combined.min.css" rel="stylesheet">
</head>
<body>
<div ng-controller="ModalDemoCtrl">
<button class="btn" ng-click="open()">Open me!</button>
<div ng-show="selected">Selection from a modal: {{ selected }}</div>
</div>
</body>
</html>
modal.html:
<div class="modal-header">
<h3>I'm a modal!</h3>
</div>
<textarea ng-model="text"></textarea>
<div class="modal-footer">
<button class="btn btn-primary" ng-click="ok()">OK</button>
<button class="btn btn-warning" ng-click="cancel()">Cancel</button>
</div>
Why I can't get the $scope.text defined in ModalInstanceCtrl, even though I can use $scope.ok and $scope.cancel?
Looks like a scope issue. I got it to work like this:
var ModalInstanceCtrl = function ($scope, $modalInstance) {
$scope.input = {};
$scope.ok = function () {
alert($scope.input.abc);
};
$scope.cancel = function () {
$modalInstance.dismiss('cancel');
};
};
HTML:
<textarea ng-model="input.abc"></textarea>
Update Nov 2014: the issue is fixed with angular-ui-bootstrap 0.12.0 - the transclusion scope is merged with the controller's scope. There is no need to do anything. Just stay with:
<textarea ng-model="text"></textarea>
Before 0.12.0:
Angular-UI modals are using transclusion to attach modal content, which means any new scope entries made within modal are created in child scope.
You should use inheritance and initialize empty text entry in parent $scope
or you can explicitly attach the input to parent scope:
<textarea ng-model="$parent.text"></textarea>
Let'me try to explain the reason. ui-bootstrap modal sourcecode:
.directive('modalWindow', ['$modalStack', '$timeout', function ($modalStack, $timeout) {
return {
restrict: 'EA',
scope: {
index: '#',
animate: '='
},
replace: true,
transclude: true,
templateUrl: function(tElement, tAttrs) {
return tAttrs.templateUrl || 'template/modal/window.html';
},
and the template sourcecode - window.html:
<div tabindex="-1" role="dialog" class="modal fade" ng-class="{in: animate}" ng-style="{'z-index': 1050 + index*10, display: 'block'}" ng-click="close($event)">
<div class="modal-dialog" ng-class="{'modal-sm': size == 'sm', 'modal-lg': size == 'lg'}"><div class="modal-content" modal-transclude></div></div>
there is a directive modal-transclude,your dialog content will insert into it, it's sourcecode:
.directive('modalTransclude', function () {
return {
link: function($scope, $element, $attrs, controller, $transclude) {
$transclude($scope.$parent, function(clone) {
$element.empty();
$element.append(clone);
});
}
};
})
now take a look at offical doc of $compile:
Transclusion Functions
When a directive requests transclusion, the compiler extracts its contents and provides
a transclusion function to the directive's link function and controller.
This transclusion function is a special linking function that will return the compiled
contents linked to a **new transclusion scope.**
transclude will create a new scope of controller scope

Resources