Multiple Modals Issue

when i try to open a modal(2) from a opened modal(1), new modal(2) gets behind the opened one(1).

@mhartington can you help me with this ?

Any help would be highly appreciated.

Can you please provide a codepen?

I ran into the same issue. But in my case, I have a service that manages my modals for me, so when I’m showing a new modal I have it loop through any existing modals to find the topmost z-index, then increment that value and apply it as the z-index of the new modal I’m about to show. So that is a functioning workaround, but ideally the $ionicModal service would handle this internally.

@mhartington here is the codepen:

"http://codepen.io/anon/pen/lLJbK

plunker:

Use case:

  1. Open Modal A then open Modal C, works fine.
  2. Now open Modal B and from there open Modal C, you will se that Modal C get behind Modal B.

Thanks for your help.

Updated the plunker to beta 11, the latest release, and it worked fine.

Not working :frowning:

First Step:

  1. Open Modal A
  2. open modal C from Modal A,
  3. close Modal C and Modal A.

WORKS FINE.

Second Step:

  1. Open Modal B,
  2. Open modal C from Modal B,

You will see that Modal C is not showing over Modal B. It is behind the Modal B.

Hmm, odd it worked for me at first.

hmm @mbuster you mentioned you created a modal management service? Care to share it?

Will do, but I need to clean it up a bit first. Some of it may not even be needed anymore.

I have changed $ionicModal service a bit and it solves my problem :smile:

Changes this code in show Method of $ionicModal service in ionic.bundle.js:

$timeout(function(){
        modalEl.addClass('ng-enter-active');
        self.scope.$parent && self.scope.$parent.$broadcast('modal.shown', self);
        self.el.classList.add('active');
        
        /**************** My Change *********************/
        var zIndex = parseInt(getStyleProperty(self.el, "z-index"));
        zIndex += 2;
        self.el.style.zIndex = zIndex;
        /**************** /My Change *********************/

   }, 20);

/**************** My Change *********************/
function getStyleProperty(el,styleProp){
        var x = el;//document.getElementById(el);

    if (window.getComputedStyle){
        var y = document.defaultView.getComputedStyle(x,null).getPropertyValue(styleProp);
    }
    else if (x.currentStyle){
        var y = x.currentStyle[styleProp];
    }
    return y;
     }
  /**************** /My Change *********************/

After having re-visited my modal service, I realized that most of it was workarounds for earlier versions of ionic (before modal show/hide returned promises, z-index issue, etc).
As such, it’s no longer needed for my own project and probably wouldn’t be helpful for anyone else.

I noticed the same issue. I was creating 2 modals when my controller started, and then showing/hiding them as needed, and removing them when the scope was destroyed. What I observed was that once a modal was shown, it’s z-index was set, and that’s what was causing the problem. As an example, suppose I have Modal A and Modal B. I always want Modal B to be on the top.

  • If I show Modal A first, then open Modal B from it, all works as expected. Modal A is set to z-index = X, and Modal B is set to X+something. So Modal B shows on top of Modal A.
  • If I show Modal B first, close it, then open Modal A. All looks good so far. If I then try to open Modal B from Modal A, it shows up BEHIND Modal A. Because Modal B’s z-index was set to X, and Modal A’s was set to X+something.

My workaround for this is to create and destroy Modal B every time it is opened/closed. This makes it get an appropriate Z-index and everything works fine.

A framework solution that would obviate the need for this workaround would be to have the Modal figure out an appropriate z-index (higher than any others) when shown.

1 Like

I just noticed the exact same issue happens with popovers. If I first show the popover in my main app, then try to launch it from a Modal, it won’t show because it’s z-order is set below the modal.

If I first launch it from the Modal, then it works fine…

Haven’t tried it yet, but I’d imagine the workaround is the same.

I am still experiencing this problem on the latest version of Ionic (1.0.0 - 14). Is there anything I can do to solve this, instead of altering the Ionic code?

I ended up writing services for both modals and popovers that create/destroy them every time. Here’s the code in case you’re interested:

// Manage opening and closing of Modals. 
.factory('ModalService', function($ionicModal, $interval, $timeout) {
    // Anything you need to do after closing the modal. 
    var afterClose = function(type, scope) {            
        if (window.StatusBar && device.platform == "iOS" && scope.modalCounter == 0) {                
          // org.apache.cordova.statusbar required
            StatusBar.show();
        }                        

        switch (type) {
            case "POST_DETAIL":            
                delete scope.post;          
                delete scope.comments; 
                delete scope.friendComments;
                delete scope.allComments;

                $interval.cancel(scope.crossFader);
                // make sure the icon is hidden at the end. 
                scope.showPostIcon = false;
                break;
        }
    };      

    var obj = {
        open: function(type, scope) {               
            var template;
            var animation = "slide-in-up";
            switch (type) {
                case "POST_DETAIL":
                    template = 'templates/post-detail.html';
                    animation = 'fade-in';
                    break;
                case "COMPOSER":
                    template = 'templates/composer.html';
                    animation = 'fade-in';
                    break;
                case "LIKES":
                    template = 'templates/likes.html';
                    break;
                case "GROUP_FROM_POST":
                    template = 'templates/group-from-post.html';
                    break;
                case "INTRO":
                    template = 'templates/intro.html';
                    break;
                case "GROUP":
                    template = 'templates/group.html';
                    break;                        
            }

            $ionicModal.fromTemplateUrl(template, {
                scope: scope,
                animation: animation,
                backdropClickToClose: false,
                hardwareBackButtonClose: true
            }).then(function(modal) {
                modal.modalType = type;
                modal.removing = false;
                scope["MODAL_" + type] = modal;

                // Track the number of modals open in this scope, so we know
                // when to re-show the status bar. 
                if (angular.isUndefined(scope.modalCounter)) {
                    scope.modalCounter = 0;
                }
                scope.modalCounter++;

                scope.$on('modal.hidden', function(event, theModal) {  
                    // remove will trigger another 'hidden' event. This 
                    // semaphore prevents us from getting into an infinite loop.                             
                    if (!theModal.removing) {                            
                        theModal.removing = true;
                        obj.remove(theModal.modalType, scope);
                    }
                });

                scope["MODAL_" + type].show();
            });     
        },
        close: function(type, scope) {     
            if (angular.isDefined(scope["MODAL_" + type])) {
                scope["MODAL_" + type].hide(); 
            }
        },
        remove: function(type, scope) {                                       
            if (angular.isDefined(scope["MODAL_" + type])) {
                scope["MODAL_" + type].remove().then(function() {
                    delete scope["MODAL_" + type];
                    scope.modalCounter--;
                    afterClose(type, scope);
                });
            }
        }, 
        isShown: function(type, scope) {
            if (angular.isDefined(scope["MODAL_" + type])) {
                return scope["MODAL_" + type].isShown();
            }
            else {
                return false;
            }
        }
    };

    return obj;

})

// Manage opening and closing of Popovers. 
.factory('PopoverService', function($ionicPopover, $interval, $timeout) {
    // Anything you need to do after closing the popover. 
    var afterClose = function() {

    };        

    var obj = {
        open: function(type, scope, target) {
            var template;
            switch (type) {
                case "POST":
                    template = 'templates/post-popover.html';
                    break;
                case "RECIPIENT":
                    template = 'templates/recipients-popover.html';
                    break;
            }

            // Setup the Post Detail popover. 
            $ionicPopover.fromTemplateUrl(template, {
                scope: scope,
                backdropClickToClose: true, 
                hardwareBackButtonClose: true
            }).then(function(popover) {
                popover.popoverType = type;
                popover.removing = false;
                scope["POPOVER_" + type] = popover;

                scope.$on('popover.hidden', function(event, thePopover) {  
                    // remove will trigger another 'hidden' event. This 
                    // semaphore prevents us from getting into an infinite loop.                             
                    if (!thePopover.removing) {
                        thePopover.removing = true;
                        obj.remove(thePopover.popoverType, scope);
                    }
                });

                scope["POPOVER_" + type].show(target);
            });                
        },
        close: function(type, scope) { 
            if (angular.isDefined(scope["POPOVER_" + type])) {
                scope["POPOVER_" + type].hide();                        
            }
        },
        remove: function(type, scope) {
            if (angular.isDefined(scope["POPOVER_" + type])) {
                scope["POPOVER_" + type].remove().then(function() {
                    delete scope["POPOVER_" + type];
                    afterClose(type, scope);
                });
            }
        },
        isShown: function(type, scope) {
            if (angular.isDefined(scope["POPOVER_" + type])) {
                return scope["POPOVER_" + type].isShown();
            }
            else {
                return false;
            }
        }
    };

    return obj;

})
1 Like

Hello guys!

I have the same error with modal z-index, but I resolved creating a factory to my modal with one method to show and other to hide.

So, in the hide method I clean my modal object and in next time, when I need, create the object again.

I create this Gists:

Work to me, I hope helped :smile:

Hi Rajat. Will be there any performance issues if we remove & create a modal instead of show & hide modal?

I didn’t notice any.

The “cleaner” way to solve this issue would be (in my opionion) to switch the order of the modals containers in the DOM.

As all modals have the same z-index, only the very last (in the DOM) .active will be displayed. Thus, if you open modal A, then close it. Then open modal B and try to open modal A, modal A will switch from .hide to .active but will still be “before” B in the DOM (which is also .active).

I wrote a quick fix before I get the time to create an issue or a pull request on github, using DOM code (which is not recommended in Angular), it gives something like this:

    var active = document.getElementsByClassName('modal-backdrop active');
    var hidden = document.getElementsByClassName('modal-backdrop hide');

    if (hidden.length > 0 && active.length > 0) {
        // browse all "active" modal nodes and put them BEFORE the very first hidden one
        // thus we make sure that any .hidden modal being shown will be displayed
        // over any currently active one
        for (var i = 0; i < active.length; ++i) {
            active[i].parentNode.insertBefore(active[i],hidden[0]);
        }
    }

I call this wherever I need to show() a “global” modal (a modal which could be open anywhere in the app, including from an already existing modal).