Dismiss Loader on Page Change in Ionic 4

August 9th 2019 Ionic 4

In Ionic 3, there was an easy way to automatically dismiss a loading overlay when the navigation to a new page completed - you only had to set the dismissOnPageChange flag when creating the overlay:

const loader = this.loadingCtrl.create({
  content: "Loading...",
  dismissOnPageChange: true
});

There's no such flag available in Ionic 4:

const loader = await this.loadingCtrl.create({
  message: "Loading..."
  // dismissOnPageChange: true - not supported
});

This is documented in the list of breaking changes. However, the whole section about it is useless at the moment for the following reasons:

  • There are no ionNavWillChange and ionNavDidChange events raised by the IonRouterOutlet which is used by default in all templates for Angular-based projects.
  • The sample code suggested to be used in Ionic 4 instead of the missing dismissOnPageChange flag doesn't provide the same functionality:
async openLoading() {
  let loading = this.loadingCtrl.create({
    message: 'Loading...'
  });

  await loading.present();

  const { role, data } = await loading.onDidDismiss();

  console.log('Loading dismissed!');
}

For new Ionic 4 projects, it's probably best to just forget about the missing functionality and always manually dismiss the loading overlay by invoking its dismiss method. However, when porting large Ionic 3 applications to Ionic 4 this can involve a lot of code changes and introduce new bugs.

To avoid that, I decided to create my own helper method for creating the loading overlay which still accepts the dismissOnPageChange flag:

async createLoadingOverlay(
  opts?: LoadingOptions & { dismissOnPageChange?: boolean }
): Promise<HTMLIonLoadingElement> {
  const loader = await this.loadingCtrl.create(opts);
  if (opts.dismissOnPageChange) {
    loader.addEventListener('ionLoadingDidPresent', () => {
      loader.setAttribute('dismissOnPageChange', 'true');
    });
  }
  return loader;
}

As you can see, I decided to store the value of the flag as an attribute of the loading element where I can inspect it when the page navigation occurs and act accordingly. Notice also, that I'm waiting for the ionLoadingDidPresent event to be raised for the element before setting the flag. Without this check it could happen that the loading overlay was flagged for dismissal before it was ever presented if it was created during the initialization of the page (e.g. in the ionViewDidEnter lifecycle method).

I still had to find the alternative to the non-existent ionNavWillChange and ionNavDidChange events. By exploring the IonRouterOutlet source code, I found the non-documented activate and deactivate events which seemed similar enough. I hooked a handler to the first one which inspects the top-most loading overlay and dismisses it if it was created with the dismissOnPageChange flag:

<ion-router-outlet (activate)="onPageActivated()"></ion-router-outlet>
async onPageActivated() {
  const topLoader = await this.loadingCtrl.getTop();
  if (topLoader && topLoader.getAttribute('dismissOnPageChange') === 'true') {
    await topLoader.dismiss();
  }
}

Based on the initial tests, this implementation works reliably enough. Of course, it won't automatically work with nested outlets (e.g. with tab pages). If necessary, similar hooks can be added to those as well. It also won't dismiss the non-top loading overlays if more than one is open at the same time. Hopefully you're not doing that.

Copyright
Creative Commons License