I'm trying to pass back a value from a uibModal. I can define the return if the user clicks the modal's close button
$scope.close = function () {
$modalInstance.close($scope.editMade);
};
But this doesn't work if the user click the backdrop area.
How can I define a return value for that particular event?
When you click on the backdrop outside, it does a dismiss internally.
Try using this inside modal:
$modalInstance.dismiss($scope.editMade);
And use this to handle data:
instance.result.then(function(){
//Get triggers when modal is closed
}, function(){
//gets triggers when modal is dismissed. You can basically handle data here
});
Check this working plunker. It uses the dismiss as I mentioned
http://embed.plnkr.co/YdWmqYPFBNbv4vhQZc4t/
Passing custom data to your parent controller when modal is dismissed:
Code in Modal Controller:
$scope.$on("modal.closing",function(){
$rootScope.$broadcast("modalClosing",$scope.editMade);
});
Then in your parent controller:
$scope.$on("modalClosing",function(event,value){
console.log(value); //value should be $scope.editMade
});
You can learn more by reading the documentation here:
https://github.com/angular-ui/bootstrap/tree/master/src/modal/docs
In the modal controller, you can do something like this:
$scope.$on('modal.closing', function(event, reason, closed) {
if (reason == "backdrop click" || reason == "escape key press")
{
event.preventDefault();
$uibModalInstance.close({"data": somedata});
}
});
This way you'll always get the success callback on modalInstance
modalInstance.result.then(function(response) {
// here response will be whatever is passed. in this sample {"data": somedata}
});
Related
The issue we are having is when we listen for the event 'ngDialog.closed', we found that it is firing multiple times. We are only opening the dialog once, so we assume that closing the same modal, this event should only fire once as well. There are multiple modals across the application, and we listen for this specific modal by attaching it to $rootScope, like so:
$rootScope.$on('ngDialog.closed', function (e, $dialog) {
console.log("CLOSED TEHE MODAL", $dialog);
console.log("EVENT", e);
if($rootScope.modalFinished && localStorage.getItem("tourComplete") !== "true"){
$rootScope.modalFinished = false;
console.log("THIS IS LOCAL STORE:", localStorage.getItem("tourComplete"));
$scope.CallMe();
}
});
The reason we are listening via $rootScope is to pass this "closed" event from one controller(the modal) to another controller(home screen) that has already been loaded before the modal controller.
We have noticed that on the 'ngDialog.closed' event, multiple events are being fired (usually up to 2 to 3 times per closing of a modal).
Here is where we open the ngDialog (in the login controller):
$state.go('app.dashboard')
if(AclService.can('edit')|| AclService.can('admin')){
$scope.$on('$stateChangeSuccess', function onStateSuccess(event, toState, toParams, fromState){
$rootScope.modalFinished = true;
CauseService.get().then(function(result){
var cause = result[0].attributes;
if(!cause.has_backlinked || !cause.has_verified_address){
// $rootScope.modalFinished = true;
ngDialog.open({
template: '../../app/views/dashboard/progress_modal.html',
showClose: false,
className: 'ngdialog-theme-default progressModal',
controller: 'ProgressModalController',
resolve:{
'Cause': ['CauseService', function(CauseService){
// console.log("CauseService in routes", CauseService.get());
return CauseService.get();
}]
}
});
}
});
});
}
Any help would be greatly appreciated.
Thanks so much.
-M-
Alright so after lots of trial and error, I concluded that due to attaching the 'ngDialog.close' event to $rootScope AND the controller we are listening on has already loaded before the modal's controller has loaded, each controller will log the event of the modal being closed.
To remedy this problem, I attached the 'ngDialog.close' event to $scope like so:
$scope.$on('ngDialog.closed', function (e, $dialog) {
if($rootScope.modalFinished && localStorage.getItem("tourComplete") !== "true"){
$rootScope.modalFinished = false;
console.log("THIS IS LOCAL STORE:", localStorage.getItem("tourComplete"));
console.log("****************NGDIALOG IS CALLED")
$scope.CallMe();
}
});
And now the desired behavior of only one 'ngDialog.close' event is fired within the dashboard controller. This solution also solves the problem of this listener being fired on other modals within other $states or controllers.
Hope this answer can help someone out and would appreciate any feedback!!!!
-M-
//In nebular (angular 4+)
// Closed event identify by below code in nebular lib in Angular 4+
this.dialogService.open(UpdateUseItemComponent, {
hasBackdrop:true ,
closeOnEsc:true,
autoFocus:true,
context: {
data: event
}
}).onClose.subscribe(result => {
this.getStock();
});
I'm using a module from the UI Boostrap extensions (http://angular-ui.github.io/bootstrap). The module actually serves as a loading dialog and is automatically closed when a set of web service data is returned to my Angular code. As the data on this page is loaded automatically the dialog comes up immediately.
All this works great when I hit the page in question for the the first time or simply refresh it. The problem occurs when I go to a deeper page and then try and navigate back to the original page (with the dialog) via the browser's back button. The dialog never goes away despite all the fact that all the data is returned and the module's dismiss() call has been made.
I've traced this down to the promise to open the dialog appears to be happening after the dismiss call but, again, only when the page is loaded via the back button. The dismiss call never closes anything because it hasn't been added yet (I've confirmed this in the debugger).
The question I have is how could I handle this? Is there a solid way to catch the completion of the page loading via Angular and double check that the dialog closed? Is there a better way via UI Bootstrap's api?
I know this is rather unusual case but any thoughts on it would be great.
Thanks!
#HankScorpio's solution is good, but I think there may be a simplified option now.
There is no need to store the current modal anymore, if you register either a $locationChangeStart or $routeChangeStart listener with $uibModalStack injected and call $uibModalStack.dismissAll(). $locationChangeStart has the benefit of working for both ngRoute and uiRoute.
i.e. If only for the one page, then in your controller you'd have:
angular.module('app')
.controller('ctrl', ['$scope', '$uibModalStack', ctrl]);
function ctrl($scope, $uibModalStack) {
$scope.$on('$locationChangeStart', handleLocationChange);
function handleLocationChange() {
$uibModalStack.dismissAll();
}
}
If you want to do this for all pages then define this in a factory that is always loaded or just an app.run code segment:
angular.module('app')
.run(['$rootScope', '$uibModalStack', setupUibModal]);
setupUibModal($rootScope, $uibModalStack) {
$rootScope.$on('$locationChangeStart', handleLocationChange);
function handleLocationChange() {
$uibModalStack.dismissAll();
}
}
Here is the simple solution when using ui-router for state change
Closing modal popup on the back button click in angularjs
App.run(['$rootScope', '$modalStack', function ($rootScope, $modalStack) {
$rootScope.$on('$stateChangeStart', function (event) {
var top = $modalStack.getTop();
if (top) {
$modalStack.dismiss(top.key);
}
})
}]);
hope this will save lot of time for people who are breaking heads
I've run into this same problem. Here's how I fixed it.
1) Create a service to abstract the opening and closing of a modal and track which one is open (necessary for step 2). Instead of calling $modal.open() directly, call ModalService.open().
Here you go, you can have the one I wrote:
(function () {
'use strict';
var theModule = angular.module('services.modalService', ['ui.bootstrap']);
theModule.factory('ModalService', function ($modal) {
var service = {};
var currentModal;
var clearModal = function () {
currentModal = undefined;
};
service.getCurrentModal = function () {
return currentModal;
};
service.open = function (options) {
currentModal = $modal.open(options);
currentModal.result['finally'](clearModal);
return currentModal;
};
return service;
});
}());
2) In a controller, add an event listener to $routeChangeStart, this event will fire whenever someone hits the back button.
$scope.$on('$routeChangeStart', function(){
var currentModal = ModalService.getCurrentModal();
if(angular.isDefined(currentModal)){
currentModal.dismiss('cancel');
}
});
3) Your modals should now close when a user hits back.
4) Enjoy.
IMPROVEMENT:
I found the answer from HankScorpio to be the best out there. I wanted to include this snippet for those using ui-router and their recommendation for stateful modals.
1) I wanted the result.finally(...) to jump to a parent state;
2) I wanted to control the closing of the modal from $stateProvider config, NOT through rigging a controller and adding a listener to $routeChangeStart
Here is an example of a state that opens (and closes) it's modal:
.state('product.detail', {
url: '/detail/{productId}',
onEnter: /*open-modal logic*/,
onExit: ['ModalService', function (ModalService) { ModalService.close()} ]
})
I made ModalService aware of $state so that the result of closing a modal could jump to a parent view:
a. Add an isStateful flag to modalService.open(...):
service.open = function (options, isStateful) {
currentModal = $uibModal.open(options);
currentModal.result.finally(function () {
clearModal(isStateful);
});
return currentModal;
};
so that clearModal will return to previous state:
var clearModal = function (isStateful) {
currentModal = undefined;
if (isStateful)
$state.go('^');
};
Finally, add the closeModal() function called above (not a "stateful" close, simply a dismissal):
service.close = function() {
if (currentModal) {
currentModal.dismiss().then(function () {
clearModal();
})
}
}
The benefits of this are that back button functionality is controlled at state config level, not through a listener.
I open a UI-Bootstrap Modal using the $modal.open({...}) method. I need this to close when the user presses the back button.
The result promise returned by the open() method is not useful in this case as it cannot detect the state change due to the back button. Right now when the back button is pressed, the state changes but the modal stays open.
Basically I am having the exact problem as this question but even though it has a selected answer, the problem is not solved as evidenced from the comments. This other question is similar but also doesn't solve the back button issue.
I need some way to detect that the current state has changed from within the controller and call $modalInstance.close() or the $scope.$close() methods.
I could listen for $stateChangeStart event and check the fromState argument to conditionally close the modal. But then this event would unnecessarily keep firing for all subsequent state changes too.
UPDATE: So I tried listening for the event and deregistered it as soon as it is fired for the first time. This way I get to listen for the back button state change and then stop it when I want. The final code for the modal state is as follows:
$stateProvider.state('itemList.itemNew', {
url: '/new',
onEnter: function($state, $modal) {
$modal.open({
templateUrl: "/static/partials/item/form.html",
controller: function($http, $scope, $modalInstance) {
$scope.editableItem = {};
$scope.saveItem = function(item) {
$http.post('/api/item', item)
.success(function(data) {
$modalInstance.close(data);
alert("Saved Successfully");
}).error(function(data) {
alert("There was an error.");
});
};
//Register listener specifically for the back button :(
deRegister = $scope.$on('$stateChangeSuccess',
function(event, toState, toParams, fromState, fromParams) {
if (toState.name === 'itemList' &&
fromState.name === 'itemList.itemNew') {
$modalInstance.close();//Close the modal
deRegister();//deRegister listener on first call
}
}
);
}
}).result.then(function() {
//Promise Resolved, Modal Closed.. So reload
$state.go("^", null, {
"reload": true
});
}, function() {
//Promise Rejected, Modal Dismissed.. no reload
$state.go("^");
});
},
});
I still think there should be a better way to do it. Constellates apparently decided to dump modal.js from ui-bootstrap altogether. Should I do the same and simply render the modal using plain Bootstrap CSS out of a <ui-view/>?
I needed to address the same issue. Maybe a slightly different setup, but I believe it may work for you.
I am using the angular-modal-service, which I believe is running on bootstrap anyway.
Inside the controller for the modal I used the following:
$scope.$on('$stateChangeStart', function() {
$scope.stateChange();
});
$scope.stateChange = function() {
// Manually hide the modal using bootstrap.
$element.modal('hide');
// Now close as normal, but give 500ms for bootstrap to animate
close('', 500);
};
The second function is just the manual way of exiting the modal as per the docs under "I don't want to use the 'data-dismiss' attribute on a button, how can I close a modal manually?". It says:
All you need to do is grab the modal element in your controller, then call the bootstrap modal function to manually close the modal. Then call the close function as normal
So now, if a state change happens (including a user-initiated back-button click), the modal gracefully zips away.
I need to open the menu automatically when navigate to a specific page.
but the event is ignored.
I created the menu controller:
.controller('MenuController', function ($scope, $ionicSideMenuDelegate) {
$scope.toggleLeft = function() {
$ionicSideMenuDelegate.toggleLeft();
}; })
and the specific page controller:
.controller('Sem_ConsultasCtrl', function ($scope) {
$scope.toggleLeft();
$scope.btn = function () { $scope.toggleLeft(); }
})
in my specific page i have a directive ng-click="btn()" wich works (toggles side-menu when click on button).
but if I call ' $scope.toggleLeft(); ' outside of btn() to automatically open the side menu when navigate to specific page nothing happens.
I found the problem:
when I call '$scope.toggleLeft();' outside of btn() the page/template still has not loaded/rendered the DOM. and when I click on button (btn()) works because DOM is already rendered.
to automatically open the side-menu I need to only call '$scope.toggleLeft();' when DOM is already and for achieve that I need to define a Watcher wich do something when occurs some modification to my template:
$timeout(function () {
$scope.toggleLeft();
});
$timeout(function () { //runs after DOM is render} );
This way, is working :)
EDIT:
I was going through my answers and I noticed that this answer was not correct.
calling $timeout triggers a digest cycle that captures differences in the DOM and updates it.
other events like clicking a button or writing in a input text triggers a digest cycle, thats why the changes only happened when clicked the button
I've got links in templates inside modals. When I click them, the current page changes, but the overlay and modal stay. I could add ng-click="dimiss()" to every link in all templates in modals, but is there a better way? E.g. to close it automatically on successful route change or add just one ng-click per template to handle all links?
If you want all the opened modals to be closed whenever a route is changed successfully, you could do it in one central place by listening to the $routeChangeSuccess event, for example in a run block of your app:
var myApp = angular.module('app', []).run(function($rootScope, $uibModalStack) {
$uibModalStack.dismissAll();
});
Here you can see that the $uibModalStack service gets injected on which you can call the dismissAll method - this call will close all the currently opened modals.
So, yes, you can handle modals closing centrally, in one place, with one line of code :-)
A better way is to see that whenever a Popup (Modal) is open, on Browser Back button click (or Keyboard Back), we stop the URL change and just close the Popup. This works for a better User Experience in my Project.
The Browser Back button works normally if there is no Modal opened.
use:
$uibModalStack.dismiss(openedModal.key);
or
$uibModalStack.dismissAll;
Sample code:
.run(['$rootScope', '$uibModalStack',
function ($rootScope, $uibModalStack) {
// close the opened modal on location change.
$rootScope.$on('$locationChangeStart', function ($event) {
var openedModal = $uibModalStack.getTop();
if (openedModal) {
if (!!$event.preventDefault) {
$event.preventDefault();
}
if (!!$event.stopPropagation) {
$event.stopPropagation();
}
$uibModalStack.dismiss(openedModal.key);
}
});
}]);
I don't actually use Angular UI Bootstrap, but from looking at the docs, it looks like there is a close() method on the $modalInstance object.
So taking the example from the docs, this should work:
var ModalInstanceCtrl = function ($scope, $modalInstance, items) {
$scope.items = items;
$scope.selected = {
item: $scope.items[0]
};
$scope.ok = function () {
$modalInstance.close($scope.selected.item);
};
$scope.cancel = function () {
$modalInstance.dismiss('cancel');
};
// this will listen for route changes and call the callback
$scope.$on('$routeChangeStart', function(){
$modalInstance.close();
});
};
Hope that helps.
I resolved this issue by doing something like this:
$rootScope.$on('$stateChangeSuccess',
function(event, toState, toParams, fromState, fromParams){
$modalStack.dismissAll();
});
I am keeping this logic in the modal controller. You can listen to $locationChangeStart event and close modal there. It is also good to remove listener after, especially if you have registered a listener on $rootScope:
angular.module('MainApp').controller('ModalCtrl',['$scope','$uibModalInstance',
function ($scope, $uibModalInstance) {
var dismissModalListener = $scope.$on('$locationChangeStart', function () {
$uibModalInstance.close();
});
$scope.$on('$destroy', function() {
dismissModalListener();
});
}]);
check for the respective route condition in the event $stateChangeSuccess and
then close the open bootstrap modals globally using the class like this:
$rootScope.$on('$stateChangeSuccess',
function(event, toState, toParams, fromState, fromParams){
//hide any open bootstrap modals
angular.element('.inmodal').hide();
});
If you want to hide any other modals such as angular material dialog ($mdDialog) & sweet alert dialog's use angular.element('.modal-dialog').hide(); & angular.element('.sweet-alert').hide();
Adding this an alternative answer.
Depending on your project, using $uibModalStack.dismissAll() could trigger an error message.
As explained by JB Nizet in this answer, it is caused by dismissAll() rejecting a promise, leading to a 'failure' callback as opposed to a 'success' callback triggered by close().
Said promise rejection could trigger a possibly unwanted error handling procedure.
Given there is no closeAll() in $uibModalStack, I used this:
var modal = $uibModalStack.getTop();
while (modal && this.close(modal.key)) {
modal = this.getTop();
}
This is the same behaivour as $uibModalStack.dismissAll() but utilizes.close() instead of .dismiss().
I couldn't find any documentation describing public methods for $uibModalStack, thus, in case anybody is interested in using/looking at other methods available on $uibModalStack.
It'll likely be located in \node-modules\angular-ui-boostrap\dist\ui-boostrap-tpls.js
and dismissAll() is # line 4349
Took me a while to find it.