ionic-framework: Ionic 2: Modal window doesn't dismiss when overriding back button on Android device.

Short description of the problem:

If you override the back button e.g.:

  registerBackButtonListener() {
    this.platform.registerBackButtonAction(() => {
      var nav = this.getNav();
      if (nav.canGoBack()) {
        nav.pop();
      }
      else {
        //... do something else...
      }
    });
  }

If a modal window is open, it is not closed and instead the page in the background will navigate back.

What behavior are you expecting?

The line: nav.canGoBack() should pop a modal view, not the page underneath.

Which Ionic Version? 1.x or 2.x 2

Run ionic info from terminal/cmd prompt: (paste output below) Your system information:

Cordova CLI: 6.1.1 Ionic Framework Version: 2.0.0-beta.9 Ionic CLI Version: 2.0.0-beta.25 Ionic App Lib Version: 2.0.0-beta.15 ios-deploy version: 1.8.6 ios-sim version: 5.0.8 OS: Mac OS X El Capitan Node Version: v4.3.0 Xcode version: Xcode 7.2.1 Build version 7C1002

About this issue

  • Original URL
  • State: closed
  • Created 8 years ago
  • Reactions: 12
  • Comments: 44 (5 by maintainers)

Most upvoted comments

In the end I have this for my back button:

   constructor(private platform: Platform, private config: ConfigService, private nfc: NfcService, private alertCtrl: AlertController,
      public events: Events, private translate: TranslateService, private fetch: ConfigFetchService, private menuList: MenuList, private ionicApp: IonicApp,
      private menuCtrl: MenuController
   ) {
      platform.ready().then(() => {
         this.config.pullVersion();
         let ready = true;

         platform.registerBackButtonAction(() => {
            Logger.log("Back button action called");

            let activePortal = ionicApp._loadingPortal.getActive() ||
               ionicApp._modalPortal.getActive() ||
               ionicApp._toastPortal.getActive() ||
               ionicApp._overlayPortal.getActive();

            if (activePortal) {
               ready = false;
               activePortal.dismiss();
               activePortal.onDidDismiss(() => { ready = true; });

               Logger.log("handled with portal");
               return;
            }

            if (menuCtrl.isOpen()) {
               menuCtrl.close();

               Logger.log("closing menu");
               return;
            }

            let view = this.nav.getActive();
            let page = view ? this.nav.getActive().instance : null;

            if (page && page.isRootPage) {
               Logger.log("Handling back button on a home page");
               this.alertCtrl.create({
                  title: translate.instant('Confirmation'),
                  message: translate.instant('Do you want to exit?'),
                  buttons: [
                     {
                        text: translate.instant('Cancel'),
                        handler: () => {
                        }
                     },
                     {
                        text: translate.instant('OK'),
                        handler: () => {
                           platform.exitApp();
                        }
                     }
                  ]
               }).present();
            }
            else if (this.nav.canGoBack() || view && view.isOverlay
            ) {
               Logger.log("popping back");
               this.nav.pop();
            }
            else if (localStorage.getItem('is_logged_in')
            ) {
               Logger.log("Returning to home page");
               this.nav.setRoot(HomePage);
            }
            else if (!localStorage.getItem('is_logged_in')) {
               Logger.log("Not yet logged in... exiting");
               platform.exitApp();
            }
            else {
               Logger.log("ERROR with back button handling");
            }

         }, 1);
....

OK, I needed it and have a workaround:

Just dismiss portals yourself:

import { Component, ViewChild } from '@angular/core';
import { Platform, Events, App, MenuController, AlertController, Nav, IonicApp } from 'ionic-angular';
import { StatusBar } from 'ionic-native';

import { TabsPage } from '../pages/tabs/tabs';


@Component({
  template: `<ion-nav id="nav" #content [root]="rootPage"></ion-nav>`
})
export class MyApp {
  rootPage = TabsPage;
  @ViewChild(Nav) nav: Nav;

  constructor(private platform: Platform, private alertCtrl: AlertController,
    public events: Events, private app: App, private ionicApp: IonicApp,
    private menuCtrl: MenuController) {
    platform.ready().then(() => {
      // Okay, so the platform is ready and our plugins are available.
      // Here you can do any higher level native things you might need.
      StatusBar.styleDefault();


      let ready = true;
      document.onkeypress = (event) => {
        if (!event) return;

        if (event.keyCode >= 48 && event.keyCode <= 57) {

          console.log(this.app);
          console.log(this.ionicApp);
          console.log(this.nav);

          let activePortal = this.ionicApp._loadingPortal.getActive() ||
            this.ionicApp._modalPortal.getActive() ||
            this.ionicApp._toastPortal.getActive() ||
            this.ionicApp._overlayPortal.getActive();

          if (activePortal) {
            ready = false;
            activePortal.dismiss();
            activePortal.onDidDismiss(() => { ready = true; });
          }
          else { .... your default code here }
        }
      };
    });
  }
}

For testing I used html windows and keypress event, but you can define this function for registering back button. There is ready param, which do not try to dismiss portal if we in process of dismiss…

If this can help, that’s what I have for the current Ionic 2 version, based on @laserus 's code. My use case here is that I want the user to be directed back to the home page if they have clicked a menu item.

 constructor(public platform: Platform, private ionicApp: IonicApp, private menuCtrl: MenuController) {
    this.initializeApp();

    // used for an example of ngFor and navigation
    this.pages = [
      { title: 'Page One', component: Page1, name: 'Page1' },
      { title: 'Page Two', component: Page2, name: 'Page2'}
    ];
  }

  initializeApp() {
    this.platform.ready().then(() => {
      // Okay, so the platform is ready and our plugins are available.
      // Here you can do any higher level native things you might need.

      StatusBar.styleDefault();
      // Splashscreen.hide();
      this.hideSplashScreen();

      // let view = this.nav.getActive();
      // let currentRootPage = view.component.name;

      this.nav.getByIndex(0).component.id=1;
      this.platform.registerBackButtonAction(() => {

        let activePortal = this.ionicApp._loadingPortal.getActive() ||
          this.ionicApp._modalPortal.getActive() ||
          this.ionicApp._toastPortal.getActive() ||
          this.ionicApp._overlayPortal.getActive();

        let view = this.nav.getActive();
        let currentRootPage = view.component;


        if (activePortal) {
          activePortal.dismiss();
        }
        else if (this.menuCtrl.isOpen()) {
          this.menuCtrl.close();
        }
        else if (this.nav.canGoBack() || view && view.isOverlay) {
          this.nav.pop();
        }
        else if(currentRootPage != this.pages[0].component) { // Could any other page that you consider as your main one
          this.openPage(this.pages[0]);
        }
        else {
          this.platform.exitApp();
        }

        return;

      }, 1);

    });
  }

Hi guys, I got it working perfectly for multiple tabs, modals, menu , keyboard and other overlays with help from all the above comments. Here’s the working code for Ionic 3.0.0:

` this.platform.registerBackButtonAction(() => {

        if (this.keyboard.isOpen()) { // Handles the keyboard if open
            this.keyboard.close();
            return;
        }

        let activePortal = this.ionicApp._loadingPortal.getActive() ||
           this.ionicApp._modalPortal.getActive() ||
           this.ionicApp._toastPortal.getActive() ||
            this.ionicApp._overlayPortal.getActive();
    
       //activePortal is the active overlay like a modal,toast,etc
        if (activePortal) {
            activePortal.dismiss();
            return
        }
        else if (this.menuCtrl.isOpen()) { // Close menu if open
            this.menuCtrl.close();
            return
        }

        let view = this.nav.getActive(); // As none of the above have occurred, its either a page pushed from menu or tab
        let activeVC = this.nav.getActive(); //get the active view
       
        let page = activeVC.instance; //page is the current view's instance i.e the current component I suppose
                 

        if (!(page instanceof TabsPage)) { // Check if the current page is pushed from a menu click
            
            if (this.nav.canGoBack() || view && view.isOverlay) {
                this.nav.pop(); //pop if page can go back or if its an overlay over a menu page
            }             
            else {
                this.showAlert();
            }

            return;
        }
        
        let tabs = this.app.getActiveNav(); // So it must be a view from a tab. The current tab's nav can be accessed by this.app.getActiveNav();
      

        if (!tabs.canGoBack()) {
          
            return this.showExitAlert();
        }

      
        return tabs.pop();

        
    }, 0);
   ` 

This should work out all possible scenarios, let me know if somethig is amiss.

Hi, what was the result of the discussion with your team? Is there a consensus on how to override the back button without losing basic page back, modal dismissing and alert dismissing functionality?

Maybe a good idea is to introduce new event beforeAppClose? In which You could return false; to prevent from closing?

@laserus solution works like charm thanx buddy

thanks to all!!

Thanks, will do!

@laserus thanks. works fine for me!

@laserus thanks for posting your workaround. The activePortal part solved my issues with modals.

@lazy-ape I think currently there is a bug https://github.com/driftyco/ionic/issues/8692 that modal is not closing. So you should treat it outside of app.navPop(), but otherwise it is good.

Simplest way is to do this.ionicApp._modalPortal.getActive() and dismiss it yourself.

I have a similar approach (without modals yet) but instead of if (page instanceof TabsPage) { I put on pages confirmation variable “needConfirmationOnExit” (but more appropriate approach is to use some base class and extend).

if( page && page.needConfirmationOnExit) { /* special treatment */ }
else {....}