I'm going to share this because it took me a couple hours of research and experimenting to achieve something that in my opinion should be the default behavior.
The issue :
So let's say you want to use a modal to display some information in your Ionic PWA (In my case I was displaying a list of articles, and I wanted to display the full article in a modal so as to not completely change page). You probably have a "back" or "cancel" button somewhere in your modal, and if your screen is big enough to see the rest of the page behind you can click that to dismiss the modal. So far so good !
Now the problem is that many users will want to use the hardware back button on their mouse or their phone to dismiss the modal (especially on a small screen where the modal takes the whole screen and looks like a new page), and in that case the default behavior is that it will change the actual page that's still behind your modal to the previous page... Which is definitely not what you would expect to happen.
The solution :
From Angular's perspective it makes sense : As far as the router component is concerned you never changed page (you just put a huge popup in front of your page), and hitting back will just bring you to the previous page... So let's change that !
1. Dismiss the Pop-up when the back button is pressed
This can be done easily with the @HostListener() decorator, which allows you to listen for a DOM event and trigger the decorated method when it happens. So in our modal component we can listen for the history popState and dismiss our modal :
@HostListener('window:popstate', ['$event'])
dismissModal() {
this.modalController.dismiss();
}
Chances are you already have a method that dismisses your modal that you may call from a "cancel" or "close" button in your html. If that's the case you don't need to create a new method, you can just add the decorator to your existing method.
That should work great for dismissing the modal, except won't prevent the back button from also going back in the history and switch to the previous page.
2. Don't go back to the previous page after dismissing the modal
Unfortunately there doesn't seem to be a way to prevent the default behavior of the back button, so we'll need to be get clever. One solution is to push a "fake" state for our modal in the history when it's displayed, that way the popState event will just get rid of that fake state. We can put anything we want in the "state" parameter of history.pushState(), so we'll put a modal
boolean in case we later need to check if a specific state was created for a modal. Let's so it in our ngOnInit method :
ngOnInit() {
const modalState = {
modal : true,
desc : 'fake state for our modal'
};
history.pushState(modalState, null);
}
This is good. But there's still a tiny little problem : What happens if the user dismisses the modal without using the back button (by clicking on the close button in the modal itself, or clicking outside of the modal) ? We're left with a phantom state in our history and the next time they press back nothing will happen !
3. Remove any phantom history when the modal is dismissed
We'll need to manually cleanup the history in this case. So let's use our modal
to remove the last state if needed when we dismiss our modal in the
ngDestroy() method :
ngOnDestroy() {
if (window.history.state.modal) {
history.back();
}
}
And NOW we're good to go. We should have covered all cases and it will give the perfect illusion that you can dismiss a popup with the back button !
Here's the whole component with the constructor and our 3 methods :
import { Component, HostListener, OnDestroy, OnInit } from '@angular/core';
import { ModalController } from '@ionic/angular';
@Component({
selector: 'app-mymodal',
templateUrl: './mymodal.page.html',
styleUrls: ['./mymodal.page.scss'],
})
export class MyModalComponent implements OnInit, OnDestroy {
constructor(
private modalController: ModalController) {
}
ngOnInit() {
const modalState = {
modal : true,
desc : 'fake state for our modal'
};
history.pushState(modalState, null);
}
ngOnDestroy() {
if (window.history.state.modal) {
history.back();
}
}
@HostListener('window:popstate', ['$event'])
dismissModal() {
this.modalController.dismiss();
}
}
Thanks for reading ! So far it seems to work fine for me but let me know if I missed anything or if you have another solution.
References :
- https://github.com/ionic-team/ionic-v3/issues/563
- https://medium.com/@david.dalbusco/how-to-close-ionic-modals-using-the-hardware-back-button-aaddeb23dd35
- https://forum.ionicframework.com/t/how-to-close-modal-alert-on-back-button-in-ionic4-pwa/168633
- https://stackoverflow.com/questions/51729751/close-angular-modal-and-remain-on-same-page-on-back-button-click
Top comments (7)
Oh yeah I forgot to mention : The best solution is probably not to use modals in the first place 😉
Is this one a joke or is there an argument for this?
Thanks for breaking it down and saving us time Nicolas! Much appreciated 😊👐🏻
Hi I was having one question, when modals will be destroyed, it will create a forward button and what if we don't wanna show anything on that forward button click, it will just add a state in our history and we have click back button twice in order to go back to original page.
Thank you so much for this implementation, you saved my day!
Thank you so much for this! Smooth as butter.
I have NO SINGLE DOUBT, that you are sent from ABOVE, Thanks Man!