How to add to array if no response during 1 second

2020-08-09 06:18发布

I have an intercept that listens requests/responses.

I have tried to run spinner only if requests comes more then 1 seconds:

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

  constructor(private spinnerService: SpinnerService) {}

  intercept(
    req: HttpRequest<any>,
    next: HttpHandler
  ): Observable<HttpEvent<any>> {
    this.requests.push(req);
    this.spinnerService.isLoading.next(true);

    return new Observable((observer) => {
      next.handle(req).subscribe(
        (event) => {
          if (event instanceof HttpResponse) {
            this.removeRequest(req);
            observer.next(event);
          }
        },
        () => {
          this.removeRequest(req);
        },
        () => {
          this.removeRequest(req);
        }
      );
    });
  }

  private removeRequest(request: HttpRequest<any>) {
    const index = this.requests.indexOf(request);

    if (index >= 0) {
      this.requests.splice(index, 1);
    }

    this.spinnerService.loadingStop.next();
    this.spinnerService.loadingStop.complete();
    this.spinnerService.isLoading.next(this.requests.length > 0);
  }
}

Spinner service is:

 constructor() {
    this.isLoading
      .pipe(debounceTime(100), delay(1000), takeUntil(this.loadingStop))
      .subscribe((status: boolean) => (this.loadingStatus = status));
  }

For that I have added this:

.pipe(debounceTime(100), delay(1000), takeUntil(this.loadingStop))

But it does not work for me...How to show spinner if response comes more 1 second?

2条回答
我只想做你的唯一
2楼-- · 2020-08-09 06:36

To prevent the flickering of the loading indicator (I omitted the handling of multiple requests).

@Injectable()
export class LoadingInterceptor implements HttpInterceptor {

  constructor(private spinnerService: SpinnerService) {}

  intercept(req: HttpRequest<any>, next: HttpHandler ): Observable<HttpEvent<any>> {
    this.spinnerService.start();
    return next.handle(req).pipe(finalize( () => this.spinnerService.stop()));
  }
}

debounceTime(500) in spinner service does the trick:

export class SpinnerService {

  private readonly state = new BehaviorSubject<boolean>(true);
  readonly state$ = this.state.asObservable()
    .pipe(
       debounceTime(500), 
       distinctUntilChanged()
  );

  constructor() {}

  public start() {
    this.state.next(true);
  }

  public stop() {
    this.state.next(false);
  }
}

The component to see this in action:

export interface Post {
  id: string;
  title: string;
  body: string;
}

@Component({
  selector: 'app-posts',
  templateUrl: './posts.component.html',
  styleUrls: ['./posts.component.css'],
})
export class PostsComponent implements OnInit {
  readonly posts$: Observable<Post[]> = this.httpClient
    .get<Post[]>('https://jsonplaceholder.typicode.com/posts')
    .pipe(shareReplay(1));

  readonly state$ = this.spinnerService.state$;

  constructor(
    private spinnerService: SpinnerService,
    private httpClient: HttpClient
  ) {}

  ngOnInit() {}
}

HTML:

<p>List of Posts</p>

<ng-container *ngIf="(state$ | async);  else printResult">
  <h1>Loading...</h1>
</ng-container>

<ng-template #printResult>
  <ng-container *ngIf="posts$ | async as posts">
    <p *ngFor="let post of posts">
      {{ post.title }}
    </p>
  </ng-container>
</ng-template>

The solution via an interceptor is somewhat coarse grained. At some point you might need a finer grained solution. E.g. to show a loading indicator for multiple parallel requests/components. Another solution is given in Nil's blog post.

There are plenty solutions to your problem. Hope it helps.

查看更多
smile是对你的礼貌
3楼-- · 2020-08-09 06:51

Uses the iif operator to stop loading instantly.

this is what the interceptor should look like:

constructor(private spinnerService: SpinnerService) { }

intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
  this.spinnerService.start(request.url);

  return next.handle(request).pipe(
    finalize(() => () => this.spinnerService.stop(request.url))
  );
}

this is the loading service:

@Injectable()
export class SpinnerService {
  private _loading: BehaviorSubject<boolean>;
  private _request: Set<string>;
  private _delayTime: number;

  constructor() {
    this._loading = new BehaviorSubject(false);
    this._request = new Set();
    this._delayTime = 1000;
  }

  isLoading(time?: number): Observable<boolean> {
    return this._loading.asObservable().pipe(
      // uses switchMap to cancel the previous event
      switchMap(isLoading =>
        // use iif to put delay only for true value
        iif(
          () => isLoading,
          of(isLoading).pipe(
            delay(time !== undefined ? time : this._delayTime),
          ),
          of(isLoading),
        ),
      ),
    );
  }

  start(request: string = 'default', delayTime?: number): void {
    if (delayTime !== undefined)
      this._delayTime = delayTime;

    this._request.add(request);
    this._loading.next(true);
  }

  stop(request: string = 'default'): void {
    this._request.delete(request);

    if (!this._request.size)
      this._loading.next(false);
  }
}

and so it should look in the template

@Component({
  selector: 'my-app',
  template: `<div *ngIf="isLoading$ | async">loading...</div>`,
})
export class AppComponent  {
  isLoading$: Observable<boolean>;

  constructor(private spinnerService: SpinnerService) {
    this.isLoading$ = this.spinnerService.isLoading();
  }
}
查看更多
登录 后发表回答