我正在研究现有的Angular
应用程序。版本是Angular 4
。
该应用程序使HTTP
从各种组件调用REST API
。
我想为每个HTTP
请求显示一个自定义微调器。由于这是一个现有的应用程序,因此很多地方都会调用REST API
。在每个地方更改代码不是一个可行的选择。
我想实现一个解决这个问题的抽象解决方案。
如有任何选择,请建议。
@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);
}
}
}
Angular 4+有一个新的HttpClient,它支持HttpInterceptors。这允许您插入将在发出HTTP请求时运行的代码。
重要的是要注意HttpRequest不是长寿命的Observable,但它们在响应之后终止。此外,如果在响应返回之前取消订阅了observable,则取消请求并且不处理任何处理程序。因此,你最终可能会得到一个“悬挂”装载杆,它永远不会消失。如果您在应用程序中快速导航,通常会发生这种情况。
为了解决这个问题,我们需要创建一个新的Observable来附加拆卸逻辑。
我们返回此而不是原始的Observable。我们还需要跟踪所有请求,因为我们可能一次运行多个请求。
我们还需要一种服务,它可以保存和分享我们是否有未决请求的状态。
@Injectable()
export class MyLoaderService {
// A BehaviourSubject is an Observable with a default value
public isLoading = new BehaviorSubject<boolean>(false);
constructor() {}
}
Interceptor使用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();
};
});
}
}
最后,在我们的Component中,我们可以使用相同的MyLoaderService和async运算符,我们甚至不需要订阅。由于我们要使用的源值来自服务,因此它应该作为Observable共享,以便它获得使用它的渲染范围/区域。如果它只是一个值,它可能无法按需更新您的GUI。
@Component({...})
export class MyComponent {
constructor(public myLoaderService: MyLoaderService) {}
}
以及使用异步的示例模板
<div class="myLoadBar" *ngIf="myLoaderService.isLoading | async">Loading!</div>
我假设您知道如何正确提供服务和设置模块。您还可以在Stackblitz上看到一个工作示例
在Angular 5中出现了HttpClient
模块。你可以找到more information there。
有了这个模块,来一个叫做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>
(确保在显示此微调器的组件中注入您的服务)
这是一个基本的加载对话框,可以使用angular属性进行切换。只需将*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>
对每个页面初始化加载程序为true,然后在服务完成后设置为false:
组件顶部:
export class MyComponent implements OnInit {
loader:boolean = true;
//...
的OnInit():
await this.myService
.yourServiceCall()
.then(result => {
this.resultsSet=result);
this.loader = false; // <- hide the loader
}
.catch(error => console.log(error));
你可以使用一些css / gif来显示一个微调器,并在你的拦截器类中使用它,或者你可以简单地使用tur false来显示gif。
<root-app>
<div class="app-loading show">
<div class="spinner"></div>
</div>
</root-app>