Búsqueda de sitios web

javascript - spinner de Angular retrasado sin parpadeos


En este momento estoy tratando de usar el HttpInterceptor de Angular para mostrar un spinner una vez, para que no parpadee cada vez que se realiza un HttpRequest. Mi código actual para el interceptor.service.ts se ve así:

@Injectable()
export class InterceptorService implements HttpInterceptor {
  constructor(private spinnerService: SpinnerService) {}

  showSpinner() {
    this.spinnerService.show.next(true);
    console.log('true');
  }

  hideSpinner() {
    this.spinnerService.show.next(false);
    console.log('false');
  }

  intercept(
    req: HttpRequest<any>,
    next: HttpHandler,
  ): Observable<HttpEvent<any>> {
    const responseTimer$ = next
      .handle(req)
      .pipe(filter(e => e instanceof HttpResponse));
    timer(300)
      .pipe(takeUntil(responseTimer$))
      .subscribe(() => this.showSpinner());

    return next.handle(req).pipe(
      tap(evt => {
        if (evt instanceof HttpResponse) {
          this.hideSpinner();
        }
      }),
    );
  }
}

Pero si intento este código, la consola dice:

interceptor.service.ts:20 true
interceptor.service.ts:25 false
4 x interceptor.service.ts:20 true
4 x interceptor.service.ts:25 false
5 x interceptor.service.ts:25 false
interceptor.service.ts:25 false

Pero en mi opinión debería ser verdadero x veces y al final falso. Espero que entiendas lo que estoy tratando de lograr.

Respuesta #1

Necesitarás un contador en tu servicio interceptor, que muestre el número de solicitudes pendientes. Muestras tu spinner cuando el número es >0.


Editar:

Solo teoría del editor de texto

@Injectable()
export class InterceptorService implements HttpInterceptor {
  pendingCount$: BehaviorSubject<number> = new BehaviorSubject(0);

  constructor(private spinnerService: SpinnerService) {
    this.pendingCount$.pipe(
      map(Boolean),
      distinctUntilChanged(),
    ).subscribe(
      show => this.spinnerService.show.next(show),
    );
  }

  intercept(
    req: HttpRequest<any>,
    next: HttpHandler,
  ): Observable<HttpEvent<any>> {
  
    this.pendingCount$.next(this.pendingCount$.value + 1);

    return next.handle(req).pipe(
      tap(evt => this.pendingCount$.next(this.pendingCount$.value - 1)),
    );
  }
}

Respuesta #2

Es más fácil crear tu propio Subject que rastree cuántas solicitudes se han realizado y luego protegerte de errores lanzando un error si el conteo de seguimiento cae por debajo de cero.

class BusySubject extends Subject<number> {
    private _previous: number = 0;

    public next(value: number) {
        this._previous += value;
        if (this._previous < 0) {
            throw new Error('unexpected negative value');
        }
        return super.next(this._previous);
    }
}

Lo anterior es un tema que sumará los valores a lo largo del tiempo con una verificación de seguridad para valores por debajo de cero.

Luego puedes usar esto en un interceptor

@Injectable()
export class BusyInterceptor implements HttpInterceptor {

    private _requests: BusySubject = new BusySubject();

    public get busy(): Observable<boolean> {
        return this._requests.pipe(
            map((requests: number) => Boolean(requests)),
            distinctUntilChanged(),
        );
    }

    public intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        this._requests.next(1);
        return next.handle(req).pipe(finalize(() => this._requests.next(-1)));
    }
}

Es importante notar en lo anterior que uso distinctUntilChanged() para el indicador de ocupación, y también uso finalize() para el disparador de disminución. Estos son importantes para rastrear el estado adecuado de estar ocupado.

Lo anterior debería funcionar sin importar cuándo te suscribas a busy. Dado que el valor _previous es un estado interno del subject. Debería continuar rastreando correctamente incluso si nadie está suscrito. Funciona de manera similar a un BehaviorSubject() excepto que está usando un += para aplicar los cambios.