Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 867
Constellates Avatar asked May 05 '14 19:05

Constellates


2 Answers

Although the question is quite old, as I ran into the same situation recently, and came up with a better solution, I decided to share my results here anyway. The key point is to move the $modal.open service into resolve part of the state, which is kind of pre-load data and $promise services, and then inject the resolved modelInstance into onEnter, onExit etc. Codes might looks like as follow:

.state('items.detail', {
    url: '/{id}',
    resolve: {
        modalInstance: function(){
            return $modal.open({
                templateUrl: 'views/modal/item-detail.html',
                controller: 'itemDetailCtrl'
            })
        },
    },
    onEnter: function ($stateParams, $state, modalInstance, $resource) {
        modalInstance 
        .result.then(function () {
            $state.transitionTo('items');
        }, function () {
            $state.transitionTo('items');
        });
    },
    onExit: function (modalInstance) {
        if (modalInstance) {
            modalInstance.close();
        }
    }
})
like image 104
David Avatar answered Sep 22 '22 09:09

David


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.

like image 20
Armen Avatar answered Sep 24 '22 09:09

Armen