Dismissing All Loading Overlays in Ionic 4

August 16th 2019 Ionic 4 Async

In Ionic 4, the dismissAll method for closing all currently open loading overlays has been removed. This might not be such a bad idea since it could cause problems when used carelessly. Still, when porting an existing Ionic 3 app to Ionic 4 not having an equivalent for it available can be a problem.

I created my own replacement to make porting easier and minimize the required code changes. It is completely based on methods available in LoadingController.

async dismissAllLoaders() {
  let topLoader = await this.loadingCtrl.getTop();
  while (topLoader) {
    if (!(await topLoader.dismiss())) {
      throw new Error('Could not dismiss the topmost loader. Aborting...');
    }
    topLoader = await this.loadingCtrl.getTop();
  }
}

In a loop, I continue checking for the topmost loading overlay and closing it until there are no more loading overlays open. Additionally, I check the return value of LoadingController::dismiss which indicates whether the loading overlay has been successfully closed. If it hasn't, I throw an error to avoid the endless loop caused by repeatedly trying to close the same loading overlay without success. So far, this only happened to me when a loading overlay had already been created but not yet presented. It's unfortunate that in this case some loading overlays might remain open but I couldn't find a way to access and close them. To use this method successfully, you should always present the loading overlays immediately after you create them.

Thanks to the await operator, the code flow is easy to understand although both LoadingController::getTop and LoadingController::dismiss return promises. Without using await, it would be much more the difficult to implement an equivalent function. You couldn't even use the while loop then. The only way to achieve the looping behavior would be to use recursion instead. Here's the core recursive function for this case:

const whilePromise = (
  condition: () => Promise<boolean>,
  action: () => Promise<boolean>
) => {
  condition().then(value => {
    if (value) {
      action().then(closed => {
        if (closed) {
          whilePromise(condition, action);
        } else {
          throw new Error("Could not dismiss the topmost loader. Aborting...");
        }
      });
    }
  });
};

It accepts the loop condition and the loop action as parameters - functions which return a promise. While the condition evaluates to true, the action is executed. If the action also returns true, then the function is called recursively for the next execution of the loop, otherwise an error is thrown. Once the condition evaluates to false, the recursion ends.

With this recursive function in place, the asynchronous function above for dismissing all loading overlays can be reimplemented rather easily:

whilePromise(
  () => this.loadingCtrl.getTop().then(topLoader => topLoader != null),
  () => this.loadingCtrl.dismiss()
);

The promise continuation in the first function argument is required only to match the type definition. Although the value promised by LoadingController::getTop is truthy when not null or undefined, it is not typed as boolean without the explicit value check.

While you might think of the await operator as syntactic sugar only, there are cases when it will significantly simplify your code making it easier to understand and less likely to contain bugs. Implementing a loop with promises is certainly one of such cases.

Copyright
Creative Commons License