Angular show spinner,用于每个HTTP请求,代码更改次数更少

问题描述 投票:9回答:5

我正在研究现有的Angular应用程序。版本是Angular 4

该应用程序使HTTP从各种组件调用REST API

我想为每个HTTP请求显示一个自定义微调器。由于这是一个现有的应用程序,因此很多地方都会调用REST API。在每个地方更改代码不是一个可行的选择。

我想实现一个解决这个问题的抽象解决方案。

如有任何选择,请建议。

angular http
5个回答
16
投票

@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);
    }
  }
}

6
投票

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上看到一个工作示例


5
投票

在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>

(确保在显示此微调器的组件中注入您的服务)


2
投票

这是一个基本的加载对话框,可以使用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));

0
投票

你可以使用一些css / gif来显示一个微调器,并在你的拦截器类中使用它,或者你可以简单地使用tur false来显示gif。

   <root-app>
      <div class="app-loading show">
        <div class="spinner"></div>
      </div>
    </root-app>
热门问题
推荐问题
最新问题