Ionic 5 + Angular LoadingController with Http Interceptor Loader Present and Dismiss issue

Ionic 5 LoadingController Not working with Http Interceptor

This issue happen when i’m calling multiple http requests at same time in ngOnInit() method.

I have tried 2 different approaches

Approach 1:

public async loaderPresent(): Promise<void> {

    if (!this.isShowing) {
      this.isShowing = true;
      this.loading = await this.loadingController.create({
        cssClass: "my-custom-loader-class",
        message: "Please wait ...",
        backdropDismiss: true
      });
      return await this.loading.present();
    } else {
      this.isShowing = true;
    }
  }
public async loaderDismiss(): Promise<void> {

   // console.log('Loading dismissed ');

    if (this.loading && this.isShowing) {
      this.isShowing = false;
      await this.loadingController.dismiss();
    }
  }

In this case loder still present even all requests completed.

Note: Loader Not Dismissed when multiple http requests called in ngOnInit( ) method

Approach 2

In This Approach we are using Subject rxjs Operator

import { Subject } from ‘rxjs’;

private loadingRequestsStream$: Subject;

private initValues() {
    this.loaderElement = null;
    this.lazyDismissTimer = null;
    this.loadingRequestsStream$ = new Subject();
    this.loadingRequestsStream$.pipe(
      distinctUntilChanged(),
      concatMap(loader => {
        if (loader) {
          return this.createLoader()
        } else {
          return this.dismissLoader()
        };
      })
    )
      .subscribe(); // we do not worry of unsubscribing here since this service will always be used across the app
  };
private async createLoader(): Promise<void> {
    // we check if there is a loader already instantiated:
    if (!this.loaderElement) {
      // if not we create new loader and limit its max duration to 2000ms to prevent blocking loader to hangout indefinitely
      this.loaderElement = await this.loadingController.create({
        cssClass: "my-custom-loader-class",
        message: "Please wait ...",
        //duration: 3000
      });
      // its essential we return a Promise here as this is what concatMap will leverage for serialization
      return this.loaderElement.present();
    } else {
      // if loader element exists already we just return resolved promise:
      return Promise.resolve();
    };
  };
private async dismissLoader(): Promise<void> {
    // here we check if loader element exists and that there is no timer running already
    if (this.loaderElement && !this.lazyDismissTimer) {
      // we set the timer
      this.lazyDismissTimer = setTimeout(async () => {
        // after 700ms we dismiss our loader element:
        await this.loaderElement.dismiss();
        // nullify our properties right after dismiss promise fulfilled itself:
        this.loaderElement = null;
        clearTimeout(this.lazyDismissTimer);
        this.lazyDismissTimer = null;
        // still remember to return a promise to let concatMap know it can proceed
        return Promise.resolve();
      }, 700)
    } else {
      // if loader element does not exist or if there is already a timer running - there is nothing to dismiss, we just return empty promise
      return Promise.resolve();
    };
  };
public showLoader() {
    this.loadingRequestsStream$.next(true);
  };

  public hideLoader() {
    this.loadingRequestsStream$.next(false);
  };

In this case Loader Dismissing before All Requests completed.

Note: Loader Not Dismissed when multiple http requests called in ngOnInit( ) method

in your Approach 1:

this is wrong. you need to dismiss the presented loader.

replace this.loadingController.dismiss(); with this.loading.dismiss();

is this works ?

No Brother Still it’s not working,
Before all requests complete it’s dismissing

You mean, after first api request the loader gets dismissed before the second api call right?

Yes , i have a situation like in ngOnInit() method have to make 4 to 5 http calls, if 1st one completed then after immediately loader gets dismissed before complete remaining requests

Ok understood.
Please store the loaders in different properties, then after in your api subscription dismiss that loader.
i’m giving sample code below:

private loading: any;
public async loaderPresent(): Promise<any> {

      const loading = await this.loadingController.create({
        cssClass: "my-custom-loader-class",
        message: "Please wait ...",
        backdropDismiss: true
      });

      await loading.present();

     return loading;
  }
async getData() {
  this.loading = await this.loaderPresent();
  this.http.get('url here').subscribe(
  res => {
  if (this.loading) {
     this.loading.dismiss();
   }
}
)
}

// in this way you can re-use the loading controller.

// i think this will resolve your issue!

Hi Bro thank you for your Solution Update , But I want to use this approach in Interceptor

 constructor(private route: ActivatedRoute, public loaderService: LoaderService) { }
    intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        this.loaderService.show();
        return next
            .handle(request)
            .pipe(finalize(() => this.loaderService.hide())
            )

    }

like this

i didn’t do like this before.

i’m posting the code below please check once if that works or not.

as i mentioned above, create a loader like that then try the below approach in interceptor,

private loaderControllers = [];

 constructor(private route: ActivatedRoute, public loaderService: LoaderService) { }
    intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
       
       const loader = this.loaderService.show();
       this.loaderControllers.push(loader);
    
       return next
            .handle(request)
            .pipe(finalize(() => 
                if (this.loaderControllers.length > 0) {
                   this.loaderControllers[0].dismiss();
               }
              )
            )

    }

Try this approach. let me know if that works or not .

1 Like

Hello,

I have the same issue, I’m currently experimenting with the following.

  • The interceptor increments a BehaviorSubject counter when a new HTTP request starts
  • The interceptor decrements a BehaviorSubject counter when a HTTP request stops (finalizes)
  • AppComponent subscribes to the changes of BehaviorSubject. When the counter is 0, it removes the spinner using LoadingController.dismiss(). When the counter is 1, loader will be created

I noticed that during initialization phase my loader will stay on screen it won’t be dismissed despite logs showed that counter was decreased to 0 and dismiss() was called. The reason I found was the loader creation and presentation processes are async and they only resolve after the screen has been fully initialized. To work around this, I wait until the loader is fully created and double check the counter’s value. If it has been reset to 0 in the meantime, I blindly call dismiss().

It works OK at the moment, but it is too early to say if this is a good solution. I don’t have too many requests going on in my OnInit currently…

1 Like

Hi !
I know it’s been a while, but I’m struggling with the same case as Madhukumar, and I tried your approach, but I keep getting “TypeError: this.loaderControllers[0].dismiss is not a function”

Am I doing something wrong ?

Here is my code :

const loader = this.loader.spinnerLoading();
this.loaderControllers.push(loader);

        return next.handle(request).pipe(
            map((event: HttpEvent<any>) => {
                if (event instanceof HttpResponse) {
                    console.log('event--->>>', event);
                }
                
                return event;
            }),
            catchError((error: HttpErrorResponse) => {
                console.error(error);
                return throwError(error);
            }),
            finalize(() => {
                if (this.loaderControllers.length > 0) {
                    this.loaderControllers[0].dismiss();
                }
            })
        );

Hope we can figure it out together ^^

There should never be more than one LoadingController in existence at a given time, so I don’t think this design is feasible.

I like @battika’s general plan of adapting the “reference counting” idiom much better.

Can you share the LoaderService implementation also? I cannot get it to work.