Loading controller await problem

In my ngOnInit page I call a function to present a Loading and then call my service to load data.
The problem is that the service call ends before the loading starts.
How is it possibile with await?

loading: any;
...
ngOnInit() { 
    this.presentLoading();
    this.myService.get().subscribe((item)=>{
      console.log('dismiss');
      this.loading.dismiss();
    }, ()=>{
      this.loading.dismiss();
    })
  }

  async presentLoading() {
    this.loading = await this.loadingController.create({
      message: 'Loading...',
      duration: 20000
    });
    console.log('presentLoading')
    await this.loading.present();
  }

In console i can see:

dismiss
ERROR TypeError: Cannot read property 'dismiss' of undefined....
presentLoading

I’m using Ionic4.

You don’t have to put your loading to variable this.loading. You can dismiss it just by loading controller.

this.loadingController.dismiss();

And don’t forget to await your presentLoading. It is because your service can be running before your loading presented and service ended first.

async ngOnInit() {
	await this.presentLoading();
	.....
}

Or don’t use async / await in the first place. I think they obscure what is really going on, which can be especially troubling for people getting used to asynchronous programming in general. I think it’s much better in the long run to train yourself to think and write reactively, instead of relying on syntax sugar to perpetuate imperative programming styles that are out-of-place for web apps.

3 Likes

Maybe you should edit your comment to include the alternative code snippet you are suggesting enfix should use? It think it would be helpful.

I know the solution is to use a Promise and put everything inside the then() but I wanted to understand how it works and why it doesn’t work with async

Here’s MDN on the subject.

Because you never await the call to this.presentLoading().

Here’s how I would write this.

export class HomePage {
  result = "pending";
  winner$: Observable<string> = of("won").pipe(delay(2000));
  loser$: Observable<string> = throwError("you lose");

  constructor(private loaderer: LoadingController, private alerter: AlertController) {
  }

  doit(feeder: Observable<string>): void {
    this.loaderer.create({message: "loading..."})
      .then(lc => lc.present())
      .then(() => {
        feeder.pipe(finalize(() => this.loaderer.dismiss()))
          .subscribe(r => this.result = r, err => this.notifyError(err));
      });
  }

  notifyError(msg: string): void {
    this.result = "failed";
    this.alerter.create({message: msg}).then(dlog => dlog.present());
  }

  goWin(): void {
    this.doit(this.winner$);
  }

  goLose(): void {
    this.doit(this.loser$);
  }
}
<ion-content>
  <ion-item>
    <ion-label>{{result}}</ion-label>
  </ion-item>
  <ion-button (click)="goWin()">win</ion-button>
  <ion-button (click)="goLose()">lose</ion-button>
</ion-content>

Press the “win” button, you should see the loading indicator appear, sit there for a couple of seconds, disappear, and the result should be displayed as “won”. Press the “lose” button, the loading indicator should appear, disappear, be replaced by an alert, and the result should be displayed as “failed”.

Things to note:

  • no async / await
  • no any
  • no storing transient objects in controller properties (your loading). it becomes very difficult glancing at the code to know who is responsible for managing this object’s lifecycle, and if you mess that up, you end up with resource leaks or triggering double-dispose bugs
  • finalize operator allows centralizing instead of duplicating tear-down code
  • explicit return type declarations for all methods