1

I have implemented interceptor in my application which shows spinner on any http request and hide after getting the response. Also i have implemented counter for multiple http calls so that spinner goes off after last call.

But now in some cases , suppose i have three http async calls and i get the response of first call before my second call reach interceptor . This causing flickering of spinner on screen as it goes ON and OFF due to this scenarios.

3
  • 1
    try to change loader code to maintain count of requests. Commented Mar 8, 2018 at 9:08
  • Solution would be by using promises, so your spinner won't go off until response comes from all your requests.. check this Fiddle jsfiddle.net/Zenuka/pHEf9/21 if you provide me the code with http calls and spinner, I can show you how to implement. Commented Mar 8, 2018 at 16:18
  • @BaselIssmail yes i agree with you but the thing is http calls are made from different components(nested or parallel) so its difficult to identify that which call complete first or which is last call in this series Commented Mar 9, 2018 at 0:56

2 Answers 2

1

Based on my understanding of the question, your code is working "as expected". The flickering is not caused by a bug in your implementation, but instead by a "limit case": two successive $http calls, resulting in your loader screen to go off for a split second after the first request completes just to be reactivated soon after when the second request is made. When the two request are close enough this gives the flickering effect: your end user does not know that two sequential requests are made, he just sees a loading screen going off only to come up again soon after.
In this case, counting the open request cannot mitigate your problem: when the first promise is completed, the second one hasn't been made yet: the counter is still "one open request", so after the promise completes your logic detects that now there are 0 open requests and stops the loading screen.

For those reasons, the only viable solution I can imagine is to implement a sort of "switch off delay" on the loading screen. By making the "loading screen switch off" not immediate, you give your app the time to start the second request. In order to achieve this, you have two options.

The first and probably the cleaner one is to implement the "delay" in the component handling the spinner. Modify the .hideSpinner() method to hide the spinner after a certain time delay has passed, and modify the .displaySpinner() method so that it cancels out any pending "hideSpinner" call. Notice that this may not be viable if you are using a spinner component that you didn't implement and therefore cannot modify easily.

The second approach is to work on the interceptor side. You should already have an "end request" method that checks if the request counter has returned to 0 and in that case stops the loading screen Your code should look similar to (notice, this uses Typescript):

private startRequest(): void {
    // If this is the first request,start the spinner
    if (this.requestCount == 0) {
        this.loadingOverlayService.start();
    }

    this.requestCount++;
}
private endRequest(): void {
    // No request ongoing, so make sure we don’t go to negative count.
    // Should never happen, but it is better to be safe than sorry.
    if (this.requestCount == 0)
        return;

    this.requestCount--;
    // If it was the last request, call the service to stop the spinner.
    if (this.requestCount == 0) {
        this.loadingOverlayService.stop();
    }
}

Just add a setTimeout delay on the endRequest method. This way, the actual "is this the last request" check will be delayed, giving your app the time to start a new request before the spinner gets closed. Notice that this introduce a new problem: now any loading spinner will last 1Δ more than required, where Δ is the timeout you are using. In most of the real world cases this isn't actually a problem, you don't want your loading spinner to be "too fast" anyway in order to avoid a similar flickering problem with very short requests.

Your code should then be as follow:

private endRequest(): void {
    setTimeout(function() {
        if (this.requestCount == 0)
        return;

        this.requestCount--;

        if (this.requestCount == 0) {
            this.loadingOverlayService.stop();
        }
    }, 1000);        
}

As already stated, now the check will run a second after the request has ended. This will give your second request the time to be started and increment the counter before the handler can check if there are still other pending request to await. As a result, your loading screen should'b be rapidly closed and reopened but instead just stay open, hence removing the appearance of flickering.


PS: There is a third option that I haven't discussed because your post gave me the impression it won't be applicable to your situation, so I will just post it here in this foot notice as an hint for future readers.

IF all your requests are predetermined, meaning they can be started at the same time (no request must await for the results of the preceding one) you may be able to chain them in a cumulative promise by using $q.all(). This may avoid the flickering effect, but further tests would be required to be sure this solution fits your need. In the end, the setTimeout option is probably the most convenient one, with the best effort/cost/quality compromise.

Sign up to request clarification or add additional context in comments.

Comments

0

I also face the same situation this is how I achieve.

1- create spinner service with request count and observable as true false to show hide spinner when count is zero.

import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class SpinnerService {
  private activeRequests = 0;
  private spinnerStateSubject = new BehaviorSubject<boolean>(false);
  spinnerState$ = this.spinnerStateSubject.asObservable();

  addRequest() {
    this.activeRequests++;
    this.updateSpinnerState();
  }

  removeRequest() {
    if (this.activeRequests > 0) {
      this.activeRequests--;
    }
    this.updateSpinnerState(); 
  }

  private updateSpinnerState() {
    this.spinnerStateSubject.next(this.activeRequests > 0);
  }
}

2- In intercept add and remove request count.

  // remaning code 
  this.spinnerService.addRequest();

  return next.handle(request).pipe(
      catchError((error: HttpErrorResponse) => {
      //  remaning code 
    ),
      finalize(() => {
        this.spinnerService.removeRequest();
      })
    );

3- Show and hide the spinner by spinner state

export class SpinnerComponent {
  public showSpinner$ = this.spinnerService.spinnerState$;
  constructor(public spinnerService: SpinnerService) {}
}

HTML

<div  *ngIf="showSpinner$ | async">
    <Spinner></SPinner>
  </div>

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.