在您的应用等待从后端获取一些数据时,始终向用户显示一个视觉指示器是一个好主意。这通常不是一项难事 - 您只需在开始获取数据时显示加载指示器,并在数据到达时隐藏它。但是,在应用中的每个页面(或服务)中实际执行此操作可能是一项繁琐的任务。
幸运的是,Angular 为我们提供了工具,可以使用 HttpInterceptors 一劳永逸地解决整个应用中的此问题。
通过实现 HttpInterceptor
,我们将进入 HttpClient
管道并控制 http 请求和响应。您可以将其用于各种用途 - 例如添加标头、缓存或在错误时重定向。在我们的例子中,我们只需要知道是否有请求正在等待解决。
我们还将创建一个单独的 HttpLoaderService
来跟踪活动请求的数量,以便我们知道是否应该显示或隐藏加载指示器。以下是这两个服务的代码
http-loader.service.ts
@Injectable({
providedIn: "root"
})
export class HttpLoaderService {
activeRequests$: BehaviorSubject<number>;
isLoading$: Observable<boolean>;
constructor() {
this.activeRequests$ = new BehaviorSubject(0);
this.isLoading$ = this.activeRequests$.pipe(
map(requests => requests > 0)
);
}
public onRequestStart() {
setTimeout(() => this.activeRequests$.next(this.activeRequests$.value + 1), 10);
}
public onRequestEnd() {
setTimeout(() => this.activeRequests$.next(this.activeRequests$.value - 1), 10);
}
}
http-interceptor.service.ts
import { Injectable } from "@angular/core";
import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest, HttpResponse } from "@angular/common/http";
import { Observable } from "rxjs";
import { tap } from "rxjs/operators";
import { HttpLoaderService } from "./http-loader.service"
@Injectable()
export class HttpInterceptorService implements HttpInterceptor {
constructor(private httpLoaderService: HttpLoaderService) {
}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
this.onStart(req.url);
return next.handle(req).pipe(
tap((event: HttpEvent<any>) => {
if (event instanceof HttpResponse) {
this.onEnd(event.url);
}
}, (err: any) => {
this.onEnd(req.url);
}));
}
private onStart(url: string) {
this.httpLoaderService.onRequestStart();
}
private onEnd(url: string): void {
this.httpLoaderService.onRequestEnd();
}
}
我们应该在根模块中注册我们的拦截器。不幸的是,我们不能简单地通过 providedIn: "root"
来做到这一点,因为 http-拦截器是通过使用 HTTP_INTERCEPTORS
令牌的多提供程序注册的。这样做的原因是您可能有多个拦截器到位。注册如下所示
import { HTTP_INTERCEPTORS } from "@angular/common/http";
@NgModule({
// ...
providers: [
{
provide: HTTP_INTERCEPTORS,
useClass: HttpInterceptorService,
multi: true
}]
})
export class AppModule { }
放置指示器的最佳位置是 NativeScript 应用的根组件 - 这将确保它对所有页面都处于活动状态
app.component.html
<GridLayout>
<page-router-outlet></page-router-outlet>
<!-- Busy indicator with an overlay -->
<GridLayout *ngIf="loaderService.isLoading$ | async" backgroundColor="#33252525" (tap)="true">
<ActivityIndicator width="100" height="100" busy="true" class="activity-indicator"></ActivityIndicator>
</GridLayout>
</GridLayout>
请注意奇怪的 (tap)="true"
代码。这是可选的 - 仅当您希望在指示器显示时阻止用户与应用交互时。
从现在开始,我们无需执行任何特殊操作。只需在任何服务中使用 HttpClient
即可通知拦截器和加载服务,它们将显示加载指示器。
data-service.ts
export interface DataItem {
title: string;
body: string;
}
const URL = "https://jsonplaceholder.typicode.com/posts";
@Injectable({
providedIn: "root"
})
export class DataService {
constructor(private http: HttpClient) { }
public getItems(): Observable<DataItem[]> {
return this.http.get<DataItem[]>(URL);
}
}
注意!仅在使用 HttpClient
时才会调用 HttpInterceptors
。如果您直接使用 fetch
或 xhr
,则会绕过它。此外,如果您正在使用库或 SDK(通过您的后端)可能不使用 HttpClient
,则您需要向 HttpLoaderService
添加一些调用,以便为库发出的 http 调用显示加载指示器。
Angular 的另一个很酷的功能是 路由解析器。它们允许您在实际导航到需要数据的页面**之前**预取一些数据。它可以避免您显示空白或部分呈现的无数据页面。
实现解析器非常简单。
data-resolver.service.ts
@Injectable({
providedIn: "root"
})
export class DataResolverService implements Resolve<DataItem[]> {
constructor(private dataService: DataService) {
}
public resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<DataItem[]> {
return this.dataService.getItems();
}
}
不要忘记将解析器添加到您的 route-config
中
{ path: "items", component: ItemsComponent, resolve: { items: DataResolverService }
它也与 http-拦截器方法很好地配合使用。结果是在当前页面上看到加载指示器,并且当数据加载完成后,会触发导航并以其所有荣耀显示新页面,因为所有数据都已存在。作为奖励,您还可以拥有一个加载覆盖层,以防止用户在加载数据时点击其他按钮发出并发请求。
我们学到的内容的快速总结
奖励:在此示例中几乎没有特定于 NativeScript 的代码(实际上只有
AppComponent
中的标记)。您可以在您的 Web 或 代码共享应用 中与 NativeScript 一起重用所有学到的知识!
这是一个 NativeScript Playground 项目,您可以使用它来查看所有这些内容!