Ionic-framework: loadingController not preventing hardware back button press

Created on 17 Feb 2017  Â·  14Comments  Â·  Source: ionic-team/ionic-framework

Ionic version: (check one with "x")
[ ] 1.x
[x ] 2.x

I'm submitting a ... (check one with "x")
[x] bug report
[ ] feature request
[ ] support request => Please do not submit support requests here, use one of these channels: https://forum.ionicframework.com/ or http://ionicworldwide.herokuapp.com/

Current behavior:

With LoadingController currently been displayed, pressing the hardware back button takes you to the previous view with the loadingController still been shown.

Expected behavior:

LoadingController should prevent any hardware back button press from working OR dismiss the loadingController when the hardware back button press takes user to the previous view.

Steps to reproduce:

  • Push to a new view

  • Call loadingController.present()

  • Press the hardware back button while loadingController is still displayed

Related code:

// push to a new view
this.navCtrl.push(Filter);

// Call LoadingController
let loader = this.loadingCtrl.create({
  content: "Please wait...",
  duration: 10000 // to enable it still longer on the screen.
});

loader.present();

Other information:

Ionic info: (run ionic info from a terminal/cmd prompt and paste output below):

Cordova CLI: 6.5.0 
Ionic Framework Version: 2.0.1
Ionic CLI Version: 2.2.1
Ionic App Lib Version: 2.2.0
Ionic App Scripts Version: 1.1.1
ios-deploy version: 1.9.0 
ios-sim version: 5.0.13 
OS: macOS Sierra
Node Version: v6.9.4
Xcode version: Xcode 8.2.1 Build version 8C1002
v3

All 14 comments

@manucorporat I thought this was solved back in rc4 :(
Apparently the issue still happens. It would be best to prevent the backbutton action while loading just as it does when presenting an alert. Would you consider taking a look?
Thank you!

any update on this please?

Hi,

Any update....???

I am also facing same issue. Any update??

@manucorporat Any Update on this issue ?

and now I'm facing same issue, I hope will be update as soon. thanks

thanks :D

On 4 Dec 2017 8:57 a.m., "buchori28" notifications@github.com wrote:

and now I'm facing same issue, I hope will be update as soon. thanks

—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
https://github.com/ionic-team/ionic/issues/10484#issuecomment-348854825,
or mute the thread
https://github.com/notifications/unsubscribe-auth/APjGarR7RShpf6RL8MwyDpRvJzBHFVohks5s8223gaJpZM4MENWX
.

I'm also facing this issue.

I've found one workaround, for those who are calling loadings inside pages, not services:

  1. On app.component, configure back button action. This will check if your shown page have a backButtonAction function.
this.platform.registerBackButtonAction(() => {
    let nav = this.app.getRootNav();
    let activeView: ViewController = nav.getActive();

    if (activeView != null) {
        if (typeof activeView.instance.backButtonAction === 'function')
            activeView.instance.backButtonAction();
        else if (nav.canGoBack())
            nav.pop();
    }
})
  1. On the pages you need to show loadings, declare a loading parameter.
loading: Loading;
  1. When you need to show the loading, use loading you declared as parameter.
this.loading = this.loadingCtrl.create({...});
this.loading.present();
  1. Declare a backButtonAction method on these pages that will check if a loading is shown and dismiss it, instead of going back in navigation.
backButtonAction() {
    if (this.loading && this.loading.index != -1) {
        this.loading.dismiss();
    } else {
        // you can have another conditions here
        this.navCtrl.pop();
    }
}

However, this only works for loadings we call inside pages. I have a case where I need to show a loading inside a service. I can't manipulate the backbutton inside the service (only in app.component, where I don't have a reference for the loading).

This is a very severious bug, as it impacts directly on the user experience. Ionic team, you should fix this ASAP.

cli packages: 
    @ionic/cli-utils  : 1.19.0
    ionic (Ionic CLI) : 3.19.0

global packages:
    cordova (Cordova CLI) : 7.0.1

local packages:
    @ionic/app-scripts : 3.1.2
    Cordova Platforms  : android 6.2.2
    Ionic Framework    : ionic-angular 3.9.2

System:
    Node : v8.9.3
    npm  : 5.6.0
    OS   : Windows 10

@allanpoppe could you not just use event emitters to tell the service to hide the loader from the main app component? I'm struggling with this too ATM

@romain10009 eventually I changed my approach to something like you said, with some other cool stuff.

The ideia is to have a unique Loading inside a loadingService (or another service you usually imports in all pages and/or other services).

loading.service.ts

isLoading: boolean = false;

constructor(private loadingCtrl: LoadingController) {
    const this_ = this;
    this.platform.ready().then(function () {
        this_.configBackButton();
    });
}

showLoading(message: string = "") {
    this.loading = this.loadingCtrl.create({
        content: message,
        enableBackdropDismiss: true
    });
    this.loading.present();
    this.isLoading = true;
    return this.loading;
}

async hideLoading() {
    if (this.isLoading) await this.loading.dismiss();
    this.isLoading = false;
}

app.component.ts

constructor(private app: App, private loadingService: LoadingService, ...) {
}

async configurarBackButton() {
    this.platform.registerBackButtonAction(() => {
        if (this.loadingService.isLoading) {
            this.loadingService.hideLoading();

        } else {
            const nav = this.app._appRoot._getActivePortal() || this.app.getRootNav();
            const activeView: ViewController = nav.getActive();
                if (activeView) {
                if (typeof activeView.instance.backButtonAction === 'function')
                    activeView.instance.backButtonAction();
                else if (activeView.isOverlay)
                    activeView.dismiss();
                else if (nav.canGoBack())
                    nav.pop();
            }
        }
    });
}

somePage.ts or someService.ts

async someMethod() {
    let showLoading = true; // this will help the application only show the loading if the operation tooks too long
    let continueExecution = true; // this will help do the trick to prevent the application from doing things if the user clicked the back button or the loading's backdrop
    let loading: Loading;

    try {
        setTimeout(() => {
            if (showLoading) {
                loading = this.loadingService.showLoading("Saving..."); // or another loading message
                loading.onDidDismiss(() => { continueExecution = false }); // this does the prevent execution trick
            }
        }, 300);

        await this.save(); // the method you want to wait 

        if (!continueExecution) return; // prevent execution trick

        ... more operations after saving

    } catch (e) {
        this.errorService.manipulateError(e); // log the error in the database and/or show an error message

    } finally {
        showLoading = false;
        await this.loadingService.hideLoading();
    }
}

This issue also happens with ionic v3.20.

Credits to Ionic forum post here - Create a wrapper for loader instead

Note that using a provider that interacts with view layer isn't really what provider should do. Read the discussion here. Personally, IMO it's up to your preference. I rather follow the principle of DRY.

Step 1:

ionic g provider loader

This step will create a provider class named LoaderProvider in your project.

Step 2:

import {Injectable} from "@angular/core";
import {Loading, LoadingController, LoadingOptions, Platform} from "ionic-angular";

@Injectable()
export class LoaderProvider {
  constructor(
    private loadingCtrl: LoadingController,
    private platform: Platform
  ) { }

  create(opts?: LoadingOptions): Loading {
    let subscription;
    let loading = this.loadingCtrl.create(opts);

    loading.willEnter.subscribe(() => {
      subscription = this.platform.registerBackButtonAction(() => {
        console.log('back button pressed');
      }, 10); // make sure there is no other backButton action with a priority > 10
    });

    loading.onDidDismiss(() => {
      subscription();
    });

    return loading;
  }
}

Hello,

you can dismiss loader on hardware back button and also on page's back button by simply adding
# dismissOnPageChange: true on loading controller like below:-

loadingModal: any;
this.loadingModal = this.loadingCtrl.create(
 {
        spinner:'hide',
         content: '<img src="assets/icon/loader.gif">',
         dismissOnPageChange: true
  });
  this.loadingModal.present();

This issue has been automatically identified as an Ionic 3 issue. We recently moved Ionic 3 to its own repository. I am moving this issue to the repository for Ionic 3. Please track this issue over there.

If I've made a mistake, and if this issue is still relevant to Ionic 4, please let the Ionic Framework team know!

Thank you for using Ionic!

Was this page helpful?
0 / 5 - 0 ratings