Conditionally go back with IonBackButton

Going one step further than Ion-back-button prevent back function [ionic 7] - #7 by seferagic, I am trying to conditionally go back with IonBackButton. The scenario is the user is on an edit page and they have changed some data. I want to warn the user when they try going back that they have unsaved data.

If they cancel, I want the user to stay on the page. If they confirm, I want to continue going back.

I have two questions. I added my own click event on IonBackButton which intercepts the default back action. The first question is why doesn’t the click event bubble up to the IonBackButton logic.

The second question, is it possible to call the normal IonBackButton logic from the overridden click method?

EDIT
Some other thoughts/comments.

How are others handling this scenario? On Android, we have to also handle the hardware back button (and the back gesture). I think this should be pretty straightforward with Ionic’s useBackButton and some global state.

I am thinking I can just replicate the IonBackButton click logic in my click method as it’s pretty straightforward if there isn’t a better solution. The reason for using the IonBackButton is because it handles the swapping of the icon for Android and iOS :slight_smile:
END EDIT

Here’s a StackBlitz - Ionic v7 IonicBackButton Override - StackBlitz

And the code I have so far:

<template>
  <ion-page>
    <ion-header>
      <ion-toolbar>
        <IonButtons slot="start">
          <IonBackButton default-href="/home" @click="handleBack" />
        </IonButtons>
        <ion-title>PageOne</ion-title>
      </ion-toolbar>
    </ion-header>

    <ion-content :fullscreen="true" class="ion-padding">
      <p>This is PageOne!</p>
    </ion-content>
  </ion-page>
</template>

<script setup lang="ts">
import {
  IonContent,
  IonHeader,
  IonPage,
  IonTitle,
  IonToolbar,
  IonButton,
  IonButtons,
  IonBackButton,
  alertController,
} from '@ionic/vue';

async function handleBack(e: Event) {
  console.log('back!');

  const alert = await alertController.create({
    header: 'Confirm',
    message: 'Are you sure you want to go back?',
    buttons: [
      {
        text: 'Cancel',
        role: 'cancel',
      },
      {
        text: 'Yes',
        role: 'yes',
      },
    ],
  });

  await alert.present();
  const { role } = await alert.onDidDismiss();

  if (role === 'yes') {
    console.log('Going back!');
  } else {
    console.log('Staying here...');
  }
}
</script>
1 Like

Here is what I came up with. I ended up creating a Vue composable that handles both the back override and Android hardware/gesture back.

back-navigation.ts

import { UseBackButtonResult, useBackButton, useIonRouter } from '@ionic/vue'

interface BackNavigationOptions {
    defaultUrl?: string
    onBack?: () => Promise<BackNavigationResult>
    setHardwareCallback: boolean
}

export interface BackNavigationResult {
    stopNavigation: boolean
}

export function useBackNavigation(options: BackNavigationOptions) {
    const ionicRouter = useIonRouter()
    let backButtonResult: UseBackButtonResult | undefined = undefined

    if (options.setHardwareCallback) {
        backButtonResult = useBackButton(50, handleBack)
    }

    async function handleBack(): Promise<void> {
        let result: BackNavigationResult = { stopNavigation: false }

        if (options.onBack != null) {
            result = await options.onBack()
        }

        if (result.stopNavigation) {
            return
        }

        if (ionicRouter.canGoBack()) {
            ionicRouter.back()
        } else if (options.defaultUrl != null) {
            ionicRouter.replace(options.defaultUrl)
        }
    }

    function clearListeners(): void {
        backButtonResult?.unregister()
        backButtonResult = undefined
    }

    return {
        handleBack,
        clearListeners,
    }
}

Usage
Here is a simplified version being used. setHardwareCallback exists because I’ve got some additional abstractions where I don’t always want to register the hardware back callback.

<template>
    <IonBackButton
        color="white"
        default-href="/page1"
        @click.prevent.stop="backHandler.handleBack()"
    />
</template>

<script lang="ts" setup>
import { useBackNavigation } from '@/composables/back-navigation'

const hasChanges = false

const backHandler = useBackNavigation({
    defaultUrl: '/page1',
    onBack: async () => {
        // Some logic here to check if any changes have been made
        // and to confirm with the user if they really want to go back
        return {stopNavigation: hasChanges}
    },
    setHardwareCallback: true,
})

onIonViewDidLeave(() => {
    backHandler.clearListeners()
})

onUnmounted(() => {
    backHandler.clearListeners()
})
</script>

EDIT
Changed the priority for useBackButton to 50 so overlays that are set to 100 take priority. This is needed if for example there is an IonAlert for user confirmation of going back.

1 Like