Ionic 4: “Loading Controller” dismiss() is called before present() which will keep spinner without dismissing

拈花ヽ惹草 提交于 2019-12-18 10:54:39

问题


I used "Ionic Loading Controller" to show a spinner until the data is retrieved then it calls "dismiss()" to dismissed it. it works fine, but sometimes when the app already have the data, the "dismiss()" is called before the "create()" and "present()" is done which will keep the spinner without dismissing...

I tried to call the data inside "loadingController.present().then()", but that caused the data to be slower...

is this a bug? how to solve the this issue?

Example of my code:

customer: any;

constructor(public loadingController: LoadingController, private customerService: CustomerService)

ngOnInit() {
  this.presentLoading().then(a => consloe.log('presented'));
  this.customerService.getCustomer('1')
  .subscribe(customer => {
    this.customer = customer;
    this.loadingController.dismiss().then(a => console.log('dismissed'));
  }
}

async presentLoading() {
  const loading = await this.loadingController.create({
    message: 'wait. . .',
    duration: 5000
  });
  return await loading.present();
}

回答1:


this is how I solved my issue..

I used a boolean variable "isLoading" to change to false when dismiss() is called. after present() is finished if "isLoading" === false (means dismiss() already called) then it will dismiss immediately.

also, I wrote the code in a service so I don't have to write it again in each page.

loading.service.ts

import { Injectable } from '@angular/core';
import { LoadingController } from '@ionic/angular';

@Injectable({
  providedIn: 'root'
})
export class LoadingService {

  isLoading = false;

  constructor(public loadingController: LoadingController) { }

  async present() {
    this.isLoading = true;
    return await this.loadingController.create({
      duration: 5000,
    }).then(a => {
      a.present().then(() => {
        console.log('presented');
        if (!this.isLoading) {
          a.dismiss().then(() => console.log('abort presenting'));
        }
      });
    });
  }

  async dismiss() {
    this.isLoading = false;
    return await this.loadingController.dismiss().then(() => console.log('dismissed'));
  }
}

then just call present() and dismiss() from the page.

the example in question:

customer: any;

constructor(public loading: LoadingService, private customerService: CustomerService)

ngOnInit() {
  this.loading.present();
  this.customerService.getCustomer('1')
  .subscribe(
    customer => {
      this.customer = customer;
      this.loading.dismiss();
    },
    error => {
      console.log(error);
      this.loading.dismiss();
    }
  );

note: don't forget to add "LoadingService" to AppModule's providers




回答2:


for Ionic 4 check this solution

Source Link

  import { Component } from '@angular/core';
  import { LoadingController } from '@ionic/angular';

  @Component({
    selector: 'app-home',
    templateUrl: 'home.page.html',
    styleUrls: ['home.page.scss'],
  })
  export class HomePage {

    loaderToShow: any;

    constructor(
      public loadingController: LoadingController
    ) {
    }


    showAutoHideLoader() {
      this.loadingController.create({
        message: 'This Loader Will Auto Hide in 2 Seconds',
        duration: 20000
      }).then((res) => {
        res.present();

        res.onDidDismiss().then((dis) => {
          console.log('Loading dismissed! after 2 Seconds');
        });
      });
    }

    showLoader() {
      this.loaderToShow = this.loadingController.create({
        message: 'This Loader will Not AutoHide'
      }).then((res) => {
        res.present();

        res.onDidDismiss().then((dis) => {
          console.log('Loading dismissed! after 2 Seconds');
        });
      });
      this.hideLoader();
    }

    hideLoader() {
      setTimeout(() => {
        this.loadingController.dismiss();
      }, 4000);
    }

  }



回答3:


Here's how I've solved the same issue in my project. I use this service in the HTTP Interceptor to show the loader for all the REST API calls inside my app.

loading.service.ts

import {Injectable} from '@angular/core';
import {LoadingController} from '@ionic/angular';

@Injectable({
  providedIn: 'root'
})
export class LoadingService {
  constructor(public loadingController: LoadingController) {
  }

  async present(options: object) {
    // Dismiss all pending loaders before creating the new one
    await this.dismiss();

    await this.loadingController
      .create(options)
      .then(res => {
        res.present();
      });
  }

  /**
   * Dismiss all the pending loaders, if any
   */
  async dismiss() {
    while (await this.loadingController.getTop() !== undefined) {
      await this.loadingController.dismiss();
    }
  }
}

In the original question context this could be used like below:

...
import {LoadingService} from '/path/to/loading.service';
...
customer: any;

constructor(public loadingService: LoadingService, private customerService: CustomerService)

ngOnInit() {
  this.loadingService.present({
    message: 'wait. . .',
    duration: 5000
  });
  this.customerService.getCustomer('1')
  .subscribe(customer => {
    this.customer = customer;
    this.loadingService.dismiss();
  }
}



回答4:


While the accepted solution can work... I think it is better to just have 1 loading always. My solution dismisses a previous loading, should it exist, and creates the new one. I usually only want to display 1 loading at a time (my particular usecase), so this solution works for me.

The accepted solution poses the problem of possible orphaned loadings. But it is a good starting point 😊.

Therefore, this is my proposed injectable service (you can overcharge it with more ionic settings, if you need it. I didn't need them so I didn't add more in the present function, but they can be added accordingly):

import {Injectable} from '@angular/core';
import {LoadingController} from '@ionic/angular';

@Injectable({
    providedIn: 'root'
})
export class LoadingService {

    currentLoading = null;

    constructor(public loadingController: LoadingController) {
    }

    async present(message: string = null, duration: number = null) {

        // Dismiss previously created loading
        if (this.currentLoading != null) {
            this.currentLoading.dismiss();
        }

        this.currentLoading = await this.loadingController.create({
            duration: duration,
            message: message
        });

        return await this.currentLoading.present();
    }

    async dismiss() {
        if (this.currentLoading != null) {

            await this.loadingController.dismiss();
            this.currentLoading = null;
        }
        return;
    }

}



回答5:


Using a list worked better for me

import { Injectable } from '@angular/core';
import { LoadingController } from '@ionic/angular';

@Injectable({providedIn: 'root'})
export class LoadingService {
    private loaders = new Array<HTMLIonLoadingElement>();
    constructor(public loadingController: LoadingController) { }

    present(options?: object) {
        if (this.loaders.length === 0) {
            this.loadingController.create(options).then(loader => {
                this.loaders.push(loader);
                loader.present();
            });
        }
    }

    async dismiss() {
        if (this.loaders && this.loaders.length > 0) {
            this.loaders.forEach(async loader => {
                await loader.dismiss()
                    .then(() => {
                        loader = null;
                    })
                    .catch(e => console.log(e))
                    .finally(() => this.loaders = new Array<HTMLIonLoadingElement>());
            });
        }
    }
}



回答6:


I'm using a similar solution but relying on the Ids of the loading overlays and letting the Ionic Loading Controller manage what overlay should be on top.

LoadingService

import { Injectable } from '@angular/core';
import { LoadingController } from '@ionic/angular';

@Injectable({
  providedIn: 'root'
})
export class LoadingService {

  constructor(public loadingController: LoadingController) { }

  async present(loadingId: string, loadingMessage: string) {
    const loading = await this.loadingController.create({
      id: loadingId,
      message: loadingMessage
    });
    return await loading.present();
  }

  async dismiss(loadingId: string) {
    return await this.loadingController.dismiss(null, null, loadingId);
  }
}

Components/Services using the LoadingService

import { LoadingService } from '../loading/loading.service';

@Injectable({
  providedIn: 'root'
})
export class MessagesService {

  ...

  constructor(
    protected http: HttpClient,
    protected loading: LoadingService
  ) { }

  ...

  protected async loadMessagesOverview() {
    const operationUrl = '/v1/messages/overview';

    await this.loading.present('messagesService.loadMessagesOverview', 'Loading messages...');

    this.http.get(environment.apiUrl + operationUrl)
      .subscribe((data: Result) => {
        ...
        this.loading.dismiss('messagesService.loadMessagesOverview');
      }, error => {
        ...
        this.loading.dismiss('messagesService.loadMessagesOverview');
        console.log('Error getting messages', error);
      });
  }

}




回答7:


I was facing the same issue, maybe I have an easier and more reliable solution using ionic events itself. This worked for me. It will wait until the loader is created and only then the service call will be done, and only when the service call is complete, only then the loader is dismissed. I hope this helps..

yourFuncWithLoaderAndServiceCall(){
     this.presentLoading().then(()=>{
         this.xyzService.getData(this.ipObj).subscribe(
           res => {
            this.dismissLoading();
        this.dismissLoading().then(() => {
        this.responseObj = res;
                   })
                  }
                 });
                }

async presentLoading() {
    this.loader = await this.loadingController.create({
      translucent: true
    });
    await this.loader.present();
  }

  async dismissLoading() {
    await this.loader.dismiss();
  }



回答8:


The simple way is Add setTimeOut function :

setTimeout(() => {
      this.loading.dismiss();
    }, 2000);



回答9:


This onDidDismiss() event should created after .present() function called.

Example:

this.loader.present().then(() => {
            this.loader.onDidDismiss(() => {
                console.log('Dismiss');
            })
        });



回答10:


same problem here, and here my solution (ionic 4 and angular 7):

Started from the acepted solution.

the present creates the loading one time In the dismiss function, i set isShowing to false only if dimiss returns true

import { Injectable } from '@angular/core';
import { LoadingController } from '@ionic/angular';

@Injectable({
  providedIn: 'root'
})
export class LoadingService {

  isDismissing: boolean;
  isShowing: boolean;

  constructor(public loadingController: LoadingController) { 

  }

  async present() {
    if(this.isShowing){
      return
    }

    this.isShowing = true

    await this.loadingController.create({spinner: "dots"}).then(re => {
      re.present()
      console.log("LoadingService presented", re.id)
    })
  }

  async dismiss() {
    if(this.isShowing){
      await this.loadingController.dismiss().then(res => {
        if(res){
          this.isShowing = false
          console.log("LoadingService dismissed", res);
        }
      })
    }
  }
}



回答11:


I know this question is to ask about a year ago. I facing the same problem. I just want to post my solution. I hope the upcoming visitor will get help.

async dismissLoading() {
    console.log("dismiss");
    this.isLoading = false;
  }
 private async presentLoading(msg) {
    console.log("loading");
    const loading = await this.loadingCtrl.create({
      message: msg,
    });
    await loading.present();
    var timer = setInterval(() => {
      if (!this.isLoading) {
        loading.dismiss();
        clearInterval(timer);
        console.log("set dismiss");
      }
    }, 1000);
  }
  async loadingmsg() {
    this.isLoading = true;
    await this.presentLoading("Please wait while...");
  }

this solution work for me. Please correct me if I am wrong.




回答12:


After trying everything, here's what I finally came up with. Seems to be working well so far.

Trying to make use of setInterval with a 500ms interval. I also tried to keep the function non-async so that it may be easily used in the consuming end.

import { Injectable } from '@angular/core';
import { LoadingController } from '@ionic/angular';

@Injectable({ providedIn: 'root' })
export class UiService {
    constructor(private loading: LoadingController) { }

    private loader: HTMLIonLoadingElement;
    private loaderLoading = false;

    public showLoading(message: string) {
        this.loaderLoading = true;
        this.loading.create({
            message,
            showBackdrop: true
        }).then(load => {
            this.loader = load;
            load.present().then(() => { this.loaderLoading = false; });
        });
    }

    public dismissLoading() {
        const interval = setInterval(() => {
            if (this.loader || !this.loaderLoading) {
                this.loader.dismiss().then(() => { this.loader = null; clearInterval(interval)});
            } else if (!this.loader && !this.loaderLoading) {
                clearInterval(interval);
            }
        }, 500);
    }
}



回答13:


Alternatively, you have to change the code where call loading like below

async ngOnInit() {
  const loading = await this.loadingController.create();
  await loading.present();
  this.customerService.getCustomer('1')
  .subscribe(customer => {
    this.customer = customer;
    loading.dismiss();
  }
}



回答14:


I had the same problem, apparently I figured It out by identifying the problem first. This problem occurs when the duration of that Loader got expired so it basically got dismissed without our full control..

Now, It will work fine Until you are also using dismiss() MANUALLY.

So if you are going to use dismiss() manually with duration, remove the duration on create. then use setTimeout() perhaps

// create loader
this.loader = this.loadingCtrl.create()

// show loader
this.loader.present().then(() => {})

// add duration here
this.loaderTimeout = setTimeout(() => {
    this.hideLoader()
}, 10000)

then create your hide loader here

// prepare to hide loader manually
hideLoader() {
   if (this.loader != null) {
      this.loader.dismiss();
      this.loader = null
    }

    // cancel any timeout of the current loader
    if (this.loaderTimeout) {
      clearTimeout(this.loaderTimeout)
      this.loaderTimeout = null
    }
}


来源:https://stackoverflow.com/questions/52574448/ionic-4-loading-controller-dismiss-is-called-before-present-which-will-ke

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!