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