← All articles

Smart AlertController Refactoring with Promises in Ionic

Why handler return values do not flow through onWillDismiss, and how to wrap AlertController in a Promise for clean async refactoring.

Published
Smart AlertController Refactoring with Promises in Ionic cover image

This article is part of the Ionic Framework / Capacitor Advent Calendar 2020.


This article uses Ionic/Angular, but the same pattern applies to Ionic/React and Ionic/Vue.

With AlertController you can simply present choices to the user and get an action—for example like this:

The code that does it is just this:

let result;
const alert = await this.alertCtrl.create({
  header: 'Open the Ionic Framework link',
  message: 'You will be redirected to https://ionicframework.jp/docs/.',
  buttons: [
    {
      text: 'Cancel',
      role: 'cancel',
      handler: () => result = false,
    },
    {
      text: 'Open',
      handler: () => result = true,
    },
  ],
});
await alert.present();
await alert.onWillDismiss(); // The Promise does not resolve until the alert closes
console.log(result); // Shows the user's selection

If the user taps "Cancel," the cancel button's handler (index [0]) runs. Same for "Open." So if you wait for the alert to close with onWillDismiss, you can read the outcome from result.

But putting AlertController display and handling in the same method gets verbose, and when refactoring you often want a separate method.

Refactoring Example That Does Not Work ☒

Ideally you could receive the user's action with code like below and refactoring would be easy—but this does not work.

public async alertHandle() {
  const result = await this.alert();  // The user's selection is not returned
  console.log(result); // undefined
}

private async alert(): Promise<boolean> {
  const alert = await this.alertCtrl.create({
    header: 'Open the Ionic Framework link',
    message: 'You will be redirected to https://ionicframework.jp/docs/.',
    buttons: [
      {
        text: 'Cancel',
        role: 'cancel',
        handler: () => false,
      },
      {
        text: 'Open',
        handler: () => true,
      },
    ],
  });
  await alert.present();
  return await alert.onWillDismiss<boolean>();
}


handler does not return through, so you cannot get the result from the code above.

Refactoring Example That Works ☑

So wrap it in a Promise and capture the user's action.


public async alertHandle() {
  const result = await this.alert();  // The user's selection is returned
  console.log(result); // true || false
}

private async alert(): Promise<boolean> {
  let resolveFunction: (confirm: boolean) => void;
  const promise = new Promise<boolean>((resolve) => (resolveFunction = resolve));
  const alert = await this.alertCtrl.create({
    header: 'Open the Ionic Framework link',
    message: 'You will be redirected to https://ionicframework.jp/docs/.',
    buttons: [
      {
        text: 'Cancel',
        role: 'cancel',
        handler: () => resolveFunction(false),
      },
      {
        text: 'Open',
        handler: () => resolveFunction(true),
      },
    ],
  });
  await alert.present();
  return promise;
}


Because resolveFunction resolves the promise with a value, you can get the result this way.

Now you can separate the method that handles AlertController from the method that uses it. Personally I find AlertController logic in pages hard to follow, so moving it to a service with a typed return value is worth it. Give it a try.

See you next time.