问题
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?
回答1:
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();
}
}
回答2:
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.
来源:https://stackoverflow.com/questions/61475079/how-to-add-to-array-if-no-response-during-1-second