Constellates Constellates - 3 months ago 24
AngularJS Question

Need to close a modal on state exit

I'm using UI-Router and angular bootstrap-ui. I have a state setup to create modal 'onEnter'. I'm having problems now when I'm trying to close the modal 'onExit'. Here is the basic state. It will open a modal when 'items.detail' is entered and it will transitions to 'items' when that modal is closed or dismissed.

.state('items.detail', {
url: '/{id}',
onEnter: function ($stateParams, $state, $modal, $resource) {
var modalInstance = $modal.open({
templateUrl: 'views/modal/item-detail.html',
controller: 'itemDetailCtrl'
})
.result.then(function () {
$state.transitionTo('items');
}, function () {
$state.transitionTo('items');
});
}
})


I've tried using the onExit handler like so. But haven't been able to access the modalInstance or the scope that the modal is in from that handler. Everything I try to inject comes up undefined.

.state('items.detail', {
url: '/{id}',
onEnter: function ($stateParams, $state, $modal, $resource) {
var modalInstance = $modal.open({
templateUrl: 'views/modal/item-detail.html',
controller: 'itemDetailCtrl'
})
.result.then(function () {
$state.transitionTo('items');
}, function () {
$state.transitionTo('items');
});
},
onExit: function ($scope) {
controller: function ($scope, $modalInstance, $modal) {
$modalInstance.dismiss();
};
}
})


from within my modal's controller I've tried listening for state changes.

$scope.$on('$stateChangeStart', function() {
$modalInstance.dismiss();
});


I've tried this with both $scope.$on and $rootScope.$on and both of these work but they end up being called every time I transition between any states. This only happens however after I've opened the modal.

In case this last bit is unclear... When I refresh my angular app I can transition between all my other states with out this listener event being called but after I open that modal all of my state changes get called by that listener, even after the modal is closed.

Answer

I think you can better organize your modal opening behavior.I would not use onEnter and onExit handlers inside state definition. Instead it's better to define controller which should handle modal:

.state('items.detail', {
    url: '/{id}',
    controller:'ItemDetailsController',
    template: '<div ui-view></div>'
})

Then define your controller:

.controller('ItemDetailsController', [
    function($stateParams, $state, $modal, $resource){
        var modalInstance = $modal.open({
            templateUrl: 'views/modal/item-detail.html',
            controller: 'ModalInstanceCtrl',
            size: size,
            resolve: {
                itemId: function () {
                   return $stateParams.id;
            }
         modalInstance.result.then(function () {
                $state.go('items');
            }, function () {
                $state.go('items');
         });
      }
    });       
    }
])

Then define your ModalInstanceCtrl:

.controller('ModalInstanceCtrl', [ 
    function ($scope, $modalInstance, itemId) {

        //Find your item from somewhere using itemId and some services
        //and do your stuff


        $scope.ok = function () {
            $modalInstance.close('ok');
        };

        $scope.cancel = function () {
           $modalInstance.dismiss('cancel');
        };
    };
]);

In this way you modal will be closed within ModalInstanceCtrl, and you will not worry about state's onExit handler.

About listener added on $scope. Seems when you add the listener and never remove it whenever your state changes, this is causing memory leak and handler function gets executed every time your app changes his state! So it's better to get rid of that event listener as you don't need it actually.