DEV Community

Nicolus
Nicolus Subscriber

Posted on

Closing a modal with the back button in Ionic 5 / Angular 9

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();
  }
Enter fullscreen mode Exit fullscreen mode

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);
  }
Enter fullscreen mode Exit fullscreen mode

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();
    }
  }
Enter fullscreen mode Exit fullscreen mode

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();
  }

}

Enter fullscreen mode Exit fullscreen mode

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 :

Top comments (7)

Collapse
 
nicolus profile image
Nicolus • Edited

Oh yeah I forgot to mention : The best solution is probably not to use modals in the first place 😉

Collapse
 
tayambamwanza profile image
Tayamba Mwanza

Is this one a joke or is there an argument for this?

Collapse
 
thisdotmedia_staff profile image
This Dot Media

Thanks for breaking it down and saving us time Nicolas! Much appreciated 😊👐🏻

Collapse
 
nerdenvironment profile image
nerdenvironment

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.

Collapse
 
jeffestrong profile image
jeffestrong

Thank you so much for this implementation, you saved my day!

Collapse
 
tayambamwanza profile image
Tayamba Mwanza

Thank you so much for this! Smooth as butter.

Collapse
 
healmah profile image
HealMah

I have NO SINGLE DOUBT, that you are sent from ABOVE, Thanks Man!