2
votes

I'm using the Angular UI bootstrap modal and I ran into a bit of a problem.

I want to call a function when the bootstrap modal dismiss animation is finished. The code block below will call the cancel() function as soon as the modal starts to be dismissed - and NOT when the modal dismiss animation has finished.

Angular UI does not use events, so there is no 'hidden.bs.modal' event being fired (at least, not to my knowledge).

var instance = $modal.open({...});

instance.result.then(function(data) {
            return success(data);
        }, function() {
            return cancel();
        })

The cancel() block immediately runs when the modal starts to close. I need code to execute when the closing animation for the Bootstrap modal finishes.

How can I achieve this with angular UI?

Component for reference:

https://angular-ui.github.io/bootstrap/#/modal

Thanks!

1

1 Answers

1
votes

A little late but hope it still helps! You can hijack the uib-modal-window directive and check when its scope gets destroyed (it is an isolated scope directive). The scope is destroyed when the modal is finally removed from the document. I would also use a service to encapsulate the functionality:

Service

app.service('Modals', function ($uibModal, $q) {
  var service = this,
      // Unique class prefix
      WINDOW_CLASS_PREFIX = 'modal-window-interceptor-',
      // Map to save created modal instances (key is unique class)
      openedWindows = {};

  this.open = function (options) {
    // create unique class
    var windowClass = _.uniqueId(WINDOW_CLASS_PREFIX);

    // check if we already have a defined class
    if (options.windowClass) {
      options.windowClass += ' ' + windowClass;
    } else {
      options.windowClass = windowClass;
    }

    // create new modal instance
    var instance = $uibModal.open(options);

    // attach a new promise which will be resolved when the modal is removed
    var removedDeferred = $q.defer();
    instance.removed = removedDeferred.promise;

    // remember instance in internal map
    openedWindows[windowClass] = {
      instance: instance,
      removedDeferred: removedDeferred
    };
    return instance;
  };

  this.afterRemove = function (modalElement) {
    // get the unique window class assigned to the modal
    var windowClass = _.find(_.keys(openedWindows), function (windowClass) {
        return modalElement.hasClass(windowClass);
      });

    // check if we have found a valid class
    if (!windowClass || !openedWindows[windowClass]) {
      return;
    }

    // get the deferred object, resolve and clean up
    var removedDeferred = openedWindows[windowClass].removedDeferred;
    removedDeferred.resolve();
    delete openedWindows[windowClass];
  };

  return this;
});

Directive

app.directive('uibModalWindow', function (Modals) {
  return {
    link: function (scope, element) {
      scope.$on('$destroy', function () {
        Modals.afterRemove(element);
      });
    }
  }
});

And use it in your controller as follows:

app.controller('MainCtrl', function ($scope, Modals) {
  $scope.openModal = function () {
    var instance = Modals.open({
      template: '<div class="modal-body">Close Me</div>' +
        '<div class="modal-footer"><a class="btn btn-default" ng-click="$close()">Close</a></div>'
    });

    instance.result.finally(function () {
      alert('result');
    });

    instance.removed.then(function () {
      alert('closed');
    });
  };
});

I also wrote a blog post about it here.