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.
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
Hi guys, can someone tell me if I'm the problem here or if it's the framework? I just want to override the back button to catch app exiting, so if I'm doing something wrong, please tell me.
Hello @daveshirman ! Thanks for opening an issue with us. A modal is actually an instance of viewController
and not navController
, so something like this should work for you:
this.platform.registerBackButtonAction(() => {
this.viewController.dismiss()
else {
//... do something else...
}
});
Thanks for using Ionic and sorry for the delay on this!
Hi, thanks for your reply. But how does this actually solve the problem? I mean, how do I know programmatically whether I need to dismiss()
a viewController
OR check nav.canGoBack()
?
This seems like something the platform should supply (already does?).
Sorry for the misunderstanding! If you would like to catch a user closing your app you can simply listen for the pause cordova event. Hope that explains things better!
Hello, i am currently discussing this with my team. Also sorry for misunderstanding your issue, i understood the issue to be about catching an app exiting, my mistake.
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?
Hi, can someone please reply to me from the Ionic team? Thanks.
Hello @daveshirman ! Sorry for the delay on this issue, with the release of beta.10 last week i have been very busy with things related to the release. I plan on taking another look at this issue today. Thanks!
Hey @daveshirman at this time there is not a good, clean way to programatically detect if their is an overlay component (like modals or alerts) open (although it is something we are considering). For a workaround i would try something like
this.platform.registerBackButtonAction(() => {
try {
this.viewController.dismiss()
}
catch(e) {
... no overlay component open
}
})
Sorry for any hassle this causes, i will be closing this issue for now as it is not something that we plan on implementing soon, but will reopen this issue if something changes. Thanks for using Ionic, and again, sorry for the delay on this.
@daveshirman Did you ever find a good solution to this issue? We have been running into this issue too. Very annoying.
Hi, unfortunately not. My "solution"was to abandon overriding the back
button as it doesn't work reliably.
Don't know when/whether the guys at Ionic are intending to fix it either.
On 10 Sep 2016 2:02 a.m., "Chuckv01" [email protected] wrote:
@daveshirman https://github.com/daveshirman Did you ever find a good
solution to this issue? We have been running into this issue too. Very
annoying.—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/driftyco/ionic/issues/6982#issuecomment-246077352,
or mute the thread
https://github.com/notifications/unsubscribe-auth/ANnlXmIsXWVvJwZnMuSUFD0Em2pSvBt2ks5qogGZgaJpZM4I6Pdr
.
I had the same problem. My "solution" was: if their is an overlay component, the ion-app element has the class "disable-scroll", so i can check if the class if present or not to prevent go back.
Something like this:
this.platform.registerBackButtonAction(() => {
//https://github.com/driftyco/ionic/issues/6982
let element = document.getElementsByTagName('ion-app')[0];
let existOverlay = element.classList.contains('disable-scroll');
if(! existOverlay) {
let nav = app.getRootNav();
if(nav.canGoBack())
{
nav.pop();
}
else
{
this.confirmExit();
return false;
}
}
}, 100)
I know that is not a beautiful solution but it works. In the confirm method I show the confirm modal. Also I used a flag to prevent that users press back repeatedly and multiples modal appear.
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...
@laserus that looks promising. I will have to try it out.
@jgw96 This is still an issue for us as of RC1. I don't think the issue should be closed. We've been forced to stop overriding the android back button as modals and dialogues still do not behave properly when it is overridden and the hardware back button is pressed.
Hi, I find a way to solve it in ionic2
. I learn it from Ionic Source. I check if having overlay is showing like this:
const portal = app._appRoot._getPortal();
if(portal.length() > 0){
//have overlay
portal.pop();
}
So, I override the back button to catch app exiting like this:
platform.registerBackButtonAction(() => {
let activeVC = this.nav.getActive();
let page = activeVC.instance;
if (page instanceof TabsPage) {
try {
const portal = app._appRoot._getPortal();
if (portal.length() == 0) {
// no overlay
// do something you want
showExitingConfirm();
}
} catch (e) {
console.log("back action listener error :" + e);
}
}
// the default handler
app.navPop();
});
My english is pool. Hope it can help you.
@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 {....}
@lazy-ape sorry your approach does not work, as you using const portal = app._appRoot._getPortal();
calling this function without parameters returns OverlayPortal only, which is the same as this.ionicApp._overlayPortal.getActive();
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);
....
@laserus thanks for posting your workaround. The activePortal part solved my issues with modals.
@laserus thanks. works fine for me!
Greatly helped! Thanks a lot.
I see that others are using my piece of code and want to warn about one caveat: the onDidDismiss callback is only one per modal...
so I had one inside one component that returned value from modal an pushed it to observer and then complete the observer (memory leak). And this one in backbuttonhandler which doing nothing atm. And this one broke the whole app logic flow.
To solve it, I had to made some changes to it, but I guess onDidDismiss could completely be removed here.... need to be tested (if it affects your app)
Newby question...
Where do I implement this code? Do I need it on every window, or can I add it once globally somewhere?
@PurpleEdge2214 you can put it once in bootstrap app.component.ts (or whatever is your first component). Inside constructor.
Thanks, will do!
@laserus from where to import those services ConfigService,NfcService etc.. I found error when using those in constructor, Thanks.
@sarfraaz53 You should remove them from your code. That is piece of my code with my services. You do not need them... I just shown constructor function in order to show where you place the code, ignore everything unknown.
@laserus - in your comment about the caveat in your fix you said "To solve it, I had to made some changes to it, but I guess onDidDismiss could completely be removed here.... need to be tested". What exactly did you have to change to solve the onDidDismiss issue? I am having the same problem - once I dismiss the modal once with the back button, no modals show after that. Could you perhaps give some ideas?
@hayuki just remove: activePortal.onDidDismiss(() => { ready = true; });
ready
is not used anyway...
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);
});
}
Hey guys, here is my final solution, thanks @laserus for your help!
platform.registerBackButtonAction(() => {
let activePortal = ionicApp._loadingPortal.getActive() ||
ionicApp._modalPortal.getActive() ||
ionicApp._toastPortal.getActive() ||
ionicApp._overlayPortal.getActive();
if (activePortal) {
return activePortal.dismiss();
}
let navChild = this.nav.getActiveChildNav();
while (true) {
if (!navChild) {
break;
}
if (navChild.canGoBack()) {
return navChild.pop();
}
navChild = navChild.getActiveChildNav();
}
if (this.nav.canGoBack()) {
return this.nav.pop();
}
if (menu.isOpen()) {
return menu.close();
}
this.confirmExitApp();
}, 0);
This solution was working great up until a recent update (2.2.0+):
let activePortal = this.ionicApp._loadingPortal.getActive() ||
this.ionicApp._modalPortal.getActive() ||
this.ionicApp._toastPortal.getActive() ||
this.ionicApp._overlayPortal.getActive();
if (activePortal) {
activePortal.dismiss()
}
Now activePortal is always undefined even if I have modals open. I don't believe anything else has changed in my app. Anyone else encountering this?
@gentlemanjohn Judging from the code it should work.
If you open ionic/src/components/app/app-root.ts
, you can see that all portals are generated in template:
'<div #viewport app-viewport></div>' +
'<div #modalPortal overlay-portal></div>' +
'<div #overlayPortal overlay-portal></div>' +
'<div #loadingPortal class="loading-portal" overlay-portal></div>' +
'<div #toastPortal class="toast-portal" [overlay-portal]="10000"></div>' +
'<div class="click-block"></div>'
And next they are referenced as:
@ViewChild('modalPortal', { read: OverlayPortal }) _modalPortal: OverlayPortal;
OverlayPortal
extends NavControllerBase
which has getActive()
The only strange thing in above code is that [overlay-portal]="10000"
for toast, I guess it is a bug. Probably it does not work for toast at all. By mistake instead of defining attribute it was defined as Input of some non-existing angular2 component.
Actually, not. There is also input @Input('overlay-portal') for overlay-portal
thanks to all!!
You can run this app:
https://github.com/yanxiaojun617/ionic2_tabs/
At 2017-04-17 04:18:26,"John Murphy" notifications@github.com wrote:
This solution was working great up until the recent update (3.0.0 +):
let activePortal = ionicApp._loadingPortal.getActive() ||
ionicApp._modalPortal.getActive() ||
ionicApp._toastPortal.getActive() ||
ionicApp._overlayPortal.getActive();
if (activePortal) {
activePortal.dismiss();
}
Now activePortal is always undefined even I have modals open. I don't believe anything else has changed in my app. Anyone else encountering this?
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub, or mute the thread.
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.
@laserus solution works like charm thanx buddy
@mayurmarwa your code seems nice but I don't understand the two lines:
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
...
same variable attribution..
Maybe a good idea is to introduce new event beforeAppClose
? In which You could return false;
to prevent from closing?
Checked on Real Devices Working Perfetly
//Check Hardware Back Button Double Tap to Exit And Close Modal On Hardware Back
let lastTimeBackPress = 0;
let timePeriodToExit = 2000;
this.platform.registerBackButtonAction(() => {
let activePortal = this.ionicApp._loadingPortal.getActive() || // Close If Any Loader Active
this.ionicApp._modalPortal.getActive() || // Close If Any Modal Active
this.ionicApp._overlayPortal.getActive(); // Close If Any Overlay Active
if (activePortal) {
activePortal.dismiss();
}
else if(this.nav.canGoBack()){
this.nav.pop();
}else{
//Double check to exit app
if (new Date().getTime() - lastTimeBackPress < timePeriodToExit) {
this.platform.exitApp(); //Exit from app
} else {
this.toast.create("Press back button again to exit");
lastTimeBackPress = new Date().getTime();
}
}
});
Found a solution by creating a provider with an observable.
When back bouton is pressed from modal, it reset backbouton on classic page.
// BACK PROVIDER
export class BackProvider {
backPressed: BehaviorSubject
constructor(public http: HttpClient) {
console.log('Hello BackProvider Provider');
this.backPressed = new BehaviorSubject(false);
}
setBack(val){
if(val == true){
this.backPressed.next(true);
console.log('BACKPROVIDER SETBACK');
}
}
getBack(){
console.log('BACKPROVIDER GETBACK');
return this.backPressed.asObservable();
}
// ANY CLASSIC PAGE
constructor(public navCtrl: NavController, public navParams: NavParams, public modalCtrl: ModalController, public viewCtrl: ViewController, private backProvider: BackProvider, private platform: Platform) {
this.autoRegisterBack();
}
back(){
this.navCtrl.pop();
}
ionViewWillEnter(){
console.log('ionWillEnter');
this.platform.registerBackButtonAction(() => {
console.log('this.back called (team member page)');
this.back();
});
}
autoRegisterBack(){
this.backProvider.getBack().subscribe(val => {
console.log('autoregisterBack prend true');
if(val == true){
this.platform.registerBackButtonAction(() => {
this.back();
});
}
});
}
// MODAL PAGE
constructor(public navCtrl: NavController, public navParams: NavParams, public viewCtrl: ViewController, private backProvider: BackProvider, private platform: Platform) {
}
ionViewWillEnter(){
this.platform.registerBackButtonAction(() => {
this.dismiss();
});
}
dismiss(){
this.viewCtrl.dismiss();
this.backProvider.setBack(true);
}
Thanks Man, Helped Again @laserus
Thanks for the issue! This issue is being locked to prevent comments that are not relevant to the original issue. If this is still an issue with the latest version of Ionic, please create a new issue and ensure the template is fully filled out.
Most helpful comment
In the end I have this for my back button: