Angular show spinner для каждого HTTP-запроса с очень небольшим изменением кода

Я работаю над существующим приложением Angular. Версия Angular 4.

Приложение выполняет HTTP-вызовы к REST API из множества различных компонентов.

Я хочу показать пользовательский счетчик для каждого HTTP-запроса. Поскольку это существующее приложение, существует множество мест, где выполняются вызовы REST API. И изменение кода по одному в каждом месте не является возможным вариантом.

Я хотел бы реализовать абстрактное решение, которое решило бы эту проблему.

Пожалуйста, предложите, если какие-либо варианты.

Ответ 1

@jornare имеет хорошую идею в своем решении. Он обрабатывает дело для нескольких запросов. Однако код можно было бы написать проще, не создавая новых наблюдаемых и сохраняющих запросы в памяти. Ниже код также использует RxJS 6 с трубчатыми операторами:

import { Injectable } from '@angular/core';
import {
  HttpRequest,
  HttpHandler,
  HttpInterceptor,
  HttpResponse
} from '@angular/common/http';
import { tap, catchError } from 'rxjs/operators';
import { LoadingService } from '@app/services/loading.service';
import { of } from 'rxjs';

@Injectable()
export class LoadingInterceptor implements HttpInterceptor {
  private totalRequests = 0;

  constructor(private loadingService: LoadingService) { }

  intercept(request: HttpRequest<any>, next: HttpHandler) {
    this.totalRequests++;
    this.loadingService.setLoading(true);
    return next.handle(request).pipe(
      tap(res => {
        if (res instanceof HttpResponse) {
          this.decreaseRequests();
        }
      }),
      catchError(err => {
        this.decreaseRequests();
        throw err;
      })
    );
  }

  private decreaseRequests() {
    this.totalRequests--;
    if (this.totalRequests === 0) {
      this.loadingService.setLoading(false);
    }
  }
}

Ответ 2

Angular 4+ имеет новый HttpClient, который поддерживает HttpInterceptors. Это позволяет вам вставлять код, который будет запускаться всякий раз, когда вы делаете HTTP-запрос.

Важно отметить, что HttpRequest не являются долгоживущими наблюдаемыми, но они прекращаются после ответа. Кроме того, если наблюдаемое отписано до того, как ответ вернулся, запрос отменяется, и ни один из обработчиков не обрабатывается. Поэтому вы можете получить "висящую" панель загрузчика, которая никогда не исчезнет. Обычно это происходит, если вы перемещаетесь немного быстрее в своем приложении.

Чтобы обойти эту последнюю проблему, нам нужно создать новую Observable, чтобы иметь возможность прикреплять teardown-logic.

Мы возвращаем это, а не оригинальную Наблюдаемую. Нам также необходимо отслеживать все сделанные запросы, поскольку мы можем выполнять более одного запроса одновременно.

Нам также нужен сервис, который может хранить и сообщать о том, есть ли у нас ожидающие запросы.

@Injectable()
export class MyLoaderService {
    // A BehaviourSubject is an Observable with a default value
    public isLoading = new BehaviorSubject<boolean>(false);

    constructor() {}
}

Перехватчик использует MyLoaderService

@Injectable()
export class MyLoaderInterceptor implements HttpInterceptor {
    private requests: HttpRequest<any>[] = [];

    constructor(private loaderService: MyLoaderService) { }

    removeRequest(req: HttpRequest<any>) {
        const i = this.requests.indexOf(req);
        this.requests.splice(i, 1);
        this.loaderService.isLoading.next(this.requests.length > 0);
    }

    intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        this.requests.push(req);
        this.loaderService.isLoading.next(true);
        return Observable.create(observer => {
          const subscription = next.handle(req)
            .subscribe(
            event => {
              if (event instanceof HttpResponse) {
                this.removeRequest(req);
                observer.next(event);
              }
            },
            err => { this.removeRequest(req); observer.error(err); },
            () => { this.removeRequest(req); observer.complete(); });
          // teardown logic in case of cancelled requests
          return () => {
            this.removeRequest(req);
            subscription.unsubscribe();
          };
        });
    }
}

Наконец, в нашем Компоненте мы можем использовать тот же MyLoaderService, а с асинхронным оператором нам даже не нужно подписываться. Поскольку исходное значение, которое мы хотим использовать, принадлежит сервису, оно должно быть передано как Observable, чтобы получить область/зону рендеринга, в которой оно используется. Если это просто значение, оно может не обновлять ваш графический интерфейс так, как хотелось бы.

@Component({...})
export class MyComponent {
    constructor(public myLoaderService: MyLoaderService) {}
}

И пример шаблона с использованием асинхронного

<div class="myLoadBar" *ngIf="myLoaderService.isLoading | async">Loading!</div>

Я предполагаю, что вы знаете, как предоставлять услуги и правильно настраивать модули. Вы также можете увидеть рабочий пример на Stackblitz

Ответ 3

В Angular 5 поставляется модуль HttpClient. Вы можете найти дополнительную информацию там.

С этим модулем приходит то, что называется interceptors.

Они позволяют вам делать что-то для каждого HTTP-запроса.

Если вы мигрируете из Http в HttpClient (и вам следует, Http устарел), вы можете создать перехватчик, который может обрабатывать переменную в общем сервисе:

intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
  this.sharedService.loading = true;
  return next
    .handle(req)
    .finally(() => this.sharedService.loading = false);
}

Теперь вам просто нужно использовать эту переменную в ваших шаблонах.

<spinner *ngIf="sharedService.loading"></spinner>

(Обязательно добавьте свой сервис в компоненты, отображающие этот счетчик)

Ответ 4

Это базовое диалоговое окно загрузки, которое можно переключить с угловым свойством. Просто добавьте *ngIf="loader" в центр-загрузчик и соответствующим образом настройте свойство

.center-loader {
    font-size: large;
    position:absolute;
    z-index:1000;
    top: 50%;
    left: 50%;
    -ms-transform: translate(-50%, -50%);
    transform: translate(-50%, -50%);
}

@keyframes blink {50% { color: transparent }}
.loader__dot { animation: 1s blink infinite; font-size: x-large;}
.loader__dot:nth-child(2) { animation-delay: 250ms; font-size: x-large;}
.loader__dot:nth-child(3) { animation-delay: 500ms; font-size: x-large;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.2.3/jquery.min.js"></script>
<div class="center-loader">
  <strong>Loading
  <span class="loader__dot">.</span>
  <span class="loader__dot">.</span>
  <span class="loader__dot">.</span></strong>
</div>

Ответ 5

Вы можете использовать некоторые CSS/GIF, чтобы показать счетчик, и использовать его в своем классе перехватчика, или вы можете просто использовать Tur false, чтобы показать GIF.

   <root-app>
      <div class="app-loading show">
        <div class="spinner"></div>
      </div>
    </root-app>

Ответ 6

Зависит от того, какой подход вы используете для использования ОТДЫХА УСЛУГ

Мой подход

  • Create a component и поместите его где-нибудь на уровне приложения.
  • Create a service, который имеет счетчик с методами increment и decrements.

  • Эта служба должна решить показать loader(component) или нет, выполнив следующие шаги.

    Увеличьте счетчик каждый для одного запроса от клиента.

    Уменьшите счетчик для каждого ответа success и failure