How to close modal/Alert on back button in ionic4 (PWA)

Hi,
I am new to Ionic 4, I have managed to implement a PWA using ionic 4 with Angular.
I am having a hard time figuring out how to close modals/Alerts/ActionSheets on back-button on browser (Desktop & mobile) when served as a PWA. When I press back when a modal is open, the modal stays open and background page is navigated back.
I have tried workarounds from different forums and posts like

  1. this.platform.backButton.subscribeWithPriority(0, async () => {…

  2. fromEvent(document, ‘backButton’).subscribe(() => {…

but none of then are working for me, please can anyone help me with this, need to go production ASAP.

Currently using : ionic : 4.6.2 Angular: 7.2.2

A modal can be dismissed by calling the dismiss method on the modal controller and optionally passing any data from the modal.

Thank you for the response.
but I wanted to know, how we can trigger dismiss method on hardware back button on device and on desktop browser back navigation button is pressed.

@aaronksaunders answer is useless. Have you even read his question ?

@akshay_kanchan, did you find a solution to this issue ? I’m running into the same issue as you do.

I’m looking for a proper way to have physical backbutton and back on the browser closes modals. Can’t find a solution that fully works. Does somebody has a solution ?

Thanks

Arnaud

@connois your response to me obviously misreading the question is a bit rude and over the top. I have been supporting this community for years, so please forgive me if I make a mistake here or there no one is perfect, but that doesn’t meant you cannot be professional

1 Like

@connois, Sorry for delayed response but I did get it worked with following code in app.component’s constructor and creating a service as shown below.

app.component

    // close all popovers on back navigation, if open.
    this.router.events.subscribe((event: any): void => {
      if (event instanceof NavigationStart) {
        if (event.navigationTrigger === 'popstate') {
          this.autocloseOverlaysService.trigger();
        }
      }
    });

AutocloseOverlaysService

import { Injectable, ViewChildren, QueryList } from '@angular/core';
import { IonRouterOutlet, ActionSheetController, PopoverController, ModalController, MenuController, ToastController } from '@ionic/angular';
import { Router } from '@angular/router';

@Injectable({
  providedIn: 'root'
})
export class AutocloseOverlaysService {
  @ViewChildren(IonRouterOutlet) routerOutlets: QueryList<IonRouterOutlet>;
  lastTimeBackPress = 0;
  timePeriodToExit = 2000;

  constructor(
    private actionSheetCtrl: ActionSheetController,
    private popoverCtrl: PopoverController,
    private modalCtrl: ModalController,
    private menu: MenuController,
    private router: Router,
    private toastController: ToastController
  ) { }
  async trigger() {
    console.log('backbutton triggered');
    // close action sheet
    try {
      const element = await this.actionSheetCtrl.getTop();
      if (element) {
        element.dismiss();
        return;
      }
    } catch (error) {
    }

    // close popover
    try {
      const element = await this.popoverCtrl.getTop();
      if (element) {
        element.dismiss();
        return;
      }
    } catch (error) {
    }

    // close modal
    try {
      const element = await this.modalCtrl.getTop();
      if (element) {
        element.dismiss();
        return;
      }
    } catch (error) {
      console.log(error);

    }

    // close side menua
    try {
      const element = await this.menu.getOpen();
      if (element !== null) {
        this.menu.close();
        return;
      }
    } catch (error) {
    }
  }
}

1 Like

Looks promising ! Gonna test and will send my feedback.

1 Like

You are right. Words came out rude when I didn’t meant them like this. English is not my native language and sometime words come out wrong. Forgive me. WiIl be more careful.

2 Likes

Works good, however, it will still navigate back in addition to closing the modal, in my case i would only want it to close the modal, is there a way to stop it from navigating within the router.events.subscribe perhaps?

1 Like

If anyone needs to solve this in the future,
I had to make some modifications to @akshay_kanchan solution to make it work how I wanted it:

when opening a modal, or in ngOnInit on the modals:

     if (!window.history.state.modal) {
        const modalState = { modal: true };
        history.pushState(modalState, null);
      }

then in app.component:

  @HostListener('window:popstate', ['$event'])
  onPopState() {
    this.autocloseOverlaysService.trigger();
  }

Now it will only close the modal, and not trigger a route change in angular. And multiple opening of modals (and closing with a button) only leaves one state in the history.

8 Likes

Hey, just in case anybody is looking for a different solution, here is mine.

I use canDeactivate guard to check if parent component can be deactivated. If modal is open, canDeactivate returns false and closes the modal, but does not navigate back from parent component.

parent-component.page.ts

export class ParentComponentPage implements DeactivatableComponent {
...
    canDeactivateVar: boolean;
...
    ngOnInit() {
        this.canDeactivateVar = true;
    }

    canDeactivate(): Observable<boolean> | Promise<boolean> | boolean {
        if(!this.canDeactivateVar){
            this.dismissModal();
            return false;
        }else {
            return this.canDeactivateVar;
        }
    }

    async openModal() {
    	const modal = await this.modalController.create({
      		component: ModalComponent
	    });
        this.canDeactivateVar = false;
    	return await modal.present();
    }

	async dismissModal() {
        this.canDeactivateVar = true;
    	return await this.modalController.dismiss();
  	}

app-routing.module.ts

...
const routes: Routes = [
	{ path: 'parent-component', component:  ParentComponentPage, canDeactivate: [AuthGuard] },
]
...

auth.guard.td

...
export class AuthGuard implements CanDeactivate<DeactivatableComponent> {
...
   canDeactivate(
        component: DeactivatableComponent,
        route: ActivatedRouteSnapshot,
        state: RouterStateSnapshot
    ): Observable<boolean> | Promise<boolean> | boolean {
        return component.canDeactivate ? component.canDeactivate() : true;
    }
..
}
...

deactivatable-component.interface.ts

import { Observable } from 'rxjs';

export interface DeactivatableComponent {
 	canDeactivate: () => Observable<boolean> | Promise<boolean> | boolean;
}
2 Likes

The only Issue I had is that the DeactivateGuard needs to be @Injectable() and in the app.module.ts you need to add the guard inside the providers array… nevertheless it works like a charm, thank you so much

1 Like
`For ionic 5 user

      this.platform.backButton.subscribeWithPriority(999, async() => {
    if (this.modalCtrl.getTop()) {
      const modal = await this.modalCtrl.getTop();
      console.log(modal)
      if (modal) { 
        this.modalCtrl.dismiss();
        return;
      }else{
        if (this.router.url=="/myrootpage" ) {
          navigator['app'].exitApp();
        }else{
          this.navCtrl.pop();
        }
      }
    }else{
      if (this.router.url=="/myrootpage") {
        navigator['app'].exitApp();
      }else{
        this.navCtrl.pop();
      }
    } 
  });`
2 Likes

@akshay_kanchan your solution is working partially. Pressing back button will dismiss the modal but also going to the previous page. Any solution?
Using ionic 5, angular 12

Hey Sorry for delayed response, in theory if you modify the AutocloseOverlaysService service code to return boolean value that is, when element exists (on any type of modal/popover) return false else return true and call this in canActivate guard, it should work as you per your requirements.

So the flow would be, if route change happens, code in the canActivate guard will execute the statement
example: return await this.autocloseOverlaysService.trigger(); and the trigger method will return boolean value which will tell angular whether the route should happen or not. So if Modal/Overlay was open the route will not happen.