Merge pull request #7032 from abpframework/fix/loader-delay

- fix loader delay in http wait service
pull/7034/head
Mehmet Erim 5 years ago committed by GitHub
commit cd96ac8584
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,13 +1,14 @@
import { Injectable } from '@angular/core';
import { Injectable, Injector } from '@angular/core';
import { HttpRequest } from '@angular/common/http';
import { InternalStore } from '../utils/internal-store-utils';
import { getPathName } from '../utils/http-utils';
import { debounceTime, tap } from 'rxjs/operators';
import { map, mapTo, switchMap, takeUntil, tap } from 'rxjs/operators';
import { of, Subject, timer } from 'rxjs';
import { LOADER_DELAY } from '../tokens/lodaer-delay.token';
export interface HttpWaitState {
requests: Set<HttpRequest<any>>;
requests: HttpRequest<any>[];
filteredRequests: Array<HttpRequestInfo>;
delay: number;
}
export interface HttpRequestInfo {
method: string;
@ -18,19 +19,35 @@ export interface HttpRequestInfo {
})
export class HttpWaitService {
protected store = new InternalStore<HttpWaitState>({
requests: new Set(),
requests: [],
filteredRequests: [],
delay: 0,
});
private delay: number;
private destroy$ = new Subject();
constructor(injector: Injector) {
this.delay = injector.get(LOADER_DELAY, 500);
}
getLoading() {
return !!this.applyFilter(this.store.state.requests).length;
}
getLoading$() {
return this.store
.sliceState(({ requests }) => !!this.applyFilter(requests).length)
.pipe(this.debounceWhenLoading());
.sliceState(({ requests }) => requests)
.pipe(
map(requests => !!this.applyFilter(requests).length),
switchMap(condition =>
condition
? this.delay === 0
? of(true)
: timer(this.delay).pipe(mapTo(true), takeUntil(this.destroy$))
: of(false),
),
tap(() => this.destroy$.next()),
);
}
updateLoading$() {
@ -38,19 +55,15 @@ export class HttpWaitService {
}
clearLoading() {
this.store.patch({ requests: new Set() });
this.store.patch({ requests: [] });
}
addRequest(request: HttpRequest<any>) {
let { requests } = this.store.state;
requests = new Set(requests.values());
requests.add(request);
this.store.patch({ requests });
this.store.patch({ requests: [...this.store.state.requests, request] });
}
deleteRequest(request: HttpRequest<any>) {
const { requests } = this.store.state;
requests.delete(request);
const requests = this.store.state.requests.filter(r => r !== request);
this.store.patch({ requests });
}
@ -73,13 +86,9 @@ export class HttpWaitService {
this.store.patch({ filteredRequests });
}
setDelay(delay: number) {
this.store.patch({ delay });
}
private applyFilter(requests: Set<HttpRequest<any>>) {
private applyFilter(requests: HttpRequest<any>[]) {
const { filteredRequests } = this.store.state;
return Array.from(requests).filter(
return requests.filter(
({ method, url }) =>
!filteredRequests.find(filteredRequest =>
this.isSameRequest(filteredRequest, { method, endpoint: getPathName(url) }),
@ -91,10 +100,4 @@ export class HttpWaitService {
const { method, endpoint } = filteredRequest;
return endpoint === request.endpoint && method === request.method;
}
private debounceWhenLoading() {
return this.store.state.delay && !!this.applyFilter(this.store.state.requests).length
? debounceTime(this.store.state.delay)
: tap();
}
}

@ -1,13 +1,9 @@
import { Injectable } from '@angular/core';
import {
NavigationCancel,
NavigationEnd,
NavigationError,
NavigationStart,
Router,
} from '@angular/router';
import { filter } from 'rxjs/operators';
import { Injectable, Injector } from '@angular/core';
import { NavigationCancel, NavigationEnd, NavigationError, NavigationStart, Router } from '@angular/router';
import { filter, map, mapTo, switchMap, takeUntil, tap } from 'rxjs/operators';
import { InternalStore } from '../utils/internal-store-utils';
import { of, Subject, timer } from 'rxjs';
import { LOADER_DELAY } from '../tokens/lodaer-delay.token';
export interface RouterWaitState {
loading: boolean;
@ -18,7 +14,10 @@ export interface RouterWaitState {
})
export class RouterWaitService {
private store = new InternalStore<RouterWaitState>({ loading: false });
constructor(private router: Router) {
private destroy$ = new Subject();
private delay: number;
constructor(private router: Router, injector: Injector) {
this.delay = injector.get(LOADER_DELAY, 500);
this.router.events
.pipe(
filter(
@ -28,10 +27,18 @@ export class RouterWaitService {
event instanceof NavigationError ||
event instanceof NavigationCancel,
),
map(event => event instanceof NavigationStart),
switchMap(condition =>
condition
? this.delay === 0
? of(true)
: timer(this.delay || 0).pipe(mapTo(true), takeUntil(this.destroy$))
: of(false),
),
tap(() => this.destroy$.next()),
)
.subscribe(event => {
if (event instanceof NavigationStart) this.setLoading(true);
else this.setLoading(false);
.subscribe(status => {
this.setLoading(status);
});
}

@ -1,2 +1,3 @@
export * from './list.token';
export * from './lodaer-delay.token';
export * from './options.token';

@ -1,9 +1,10 @@
import {
ABP,
BaseCoreModule,
coreOptionsFactory,
CORE_OPTIONS,
coreOptionsFactory,
LIST_QUERY_DEBOUNCE_TIME,
LOADER_DELAY,
PermissionService,
RestService,
} from '@abp/ng.core';
@ -52,6 +53,10 @@ export class CoreTestingModule {
provide: RestService,
useClass: MockRestService,
},
{
provide: LOADER_DELAY,
useValue: 0,
},
provideRoutes(routes),
],
};

@ -1,8 +1,7 @@
import { HttpWaitService, RouterWaitService, SubscriptionService } from '@abp/ng.core';
import { ChangeDetectorRef, Component, Inject, Input, OnDestroy, OnInit } from '@angular/core';
import { ChangeDetectorRef, Component, Input, OnDestroy, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { combineLatest, Subscription, timer } from 'rxjs';
import { LOADER_DELAY } from '../../tokens/lodaer-delay.token';
@Component({
selector: 'abp-loader-bar',
@ -77,11 +76,8 @@ export class LoaderBarComponent implements OnDestroy, OnInit {
private cdRef: ChangeDetectorRef,
private subscription: SubscriptionService,
private httpWaitService: HttpWaitService,
private routerWaiterService: RouterWaitService,
@Inject(LOADER_DELAY) delay: number,
) {
this.httpWaitService.setDelay(delay);
}
private routerWaitService: RouterWaitService,
) {}
ngOnInit() {
this.subscribeLoading();
@ -89,7 +85,7 @@ export class LoaderBarComponent implements OnDestroy, OnInit {
subscribeLoading() {
this.subscription.addOne(
combineLatest([this.httpWaitService.getLoading$(), this.routerWaiterService.getLoading$()]),
combineLatest([this.httpWaitService.getLoading$(), this.routerWaitService.getLoading$()]),
([httpLoading, routerLoading]) => {
if (httpLoading || routerLoading) this.startLoading();
else this.stopLoading();

@ -4,7 +4,6 @@ import { Validation } from '@ngx-validate/core';
export interface RootParams {
httpErrorConfig: HttpErrorConfig;
validation?: Partial<Validation.Config>;
loaderDelay?: number;
}
export type ErrorScreenErrorCodes = 401 | 403 | 404 | 500;

@ -2,9 +2,8 @@ import { NavigationEnd, NavigationError, NavigationStart, Router } from '@angula
import { createComponentFactory, Spectator, SpyObject } from '@ngneat/spectator/jest';
import { Subject, timer } from 'rxjs';
import { LoaderBarComponent } from '../components/loader-bar/loader-bar.component';
import { HttpWaitService, SubscriptionService } from '@abp/ng.core';
import { HttpWaitService, LOADER_DELAY, SubscriptionService } from '@abp/ng.core';
import { HttpRequest } from '@angular/common/http';
import { LOADER_DELAY } from '../tokens/lodaer-delay.token';
describe('LoaderBarComponent', () => {
let spectator: Spectator<LoaderBarComponent>;
@ -44,12 +43,11 @@ describe('LoaderBarComponent', () => {
}, 10);
});
test.skip('should be interval unsubscribed', done => {
it('should be interval unsubscribed', done => {
spectator.detectChanges();
const httpWaitService = spectator.inject(HttpWaitService);
httpWaitService.addRequest(new HttpRequest('GET', 'test'));
expect(spectator.component.interval.closed).toBe(false);
timer(400).subscribe(() => {
expect(spectator.component.interval.closed).toBe(true);
done();

@ -36,7 +36,6 @@ import { THEME_SHARED_ROUTE_PROVIDERS } from './providers/route.provider';
import { THEME_SHARED_APPEND_CONTENT } from './tokens/append-content.token';
import { HTTP_ERROR_CONFIG, httpErrorConfigFactory } from './tokens/http-error.token';
import { DateParserFormatter } from './utils/date-parser-formatter';
import { LOADER_DELAY } from './tokens/lodaer-delay.token';
const declarationsWithExports = [
BreadcrumbComponent,
@ -78,7 +77,7 @@ export class BaseThemeSharedModule {}
})
export class ThemeSharedModule {
static forRoot(
{ httpErrorConfig, validation = {}, loaderDelay = 500 } = {} as RootParams,
{ httpErrorConfig, validation = {} } = {} as RootParams,
): ModuleWithProviders<ThemeSharedModule> {
return {
ngModule: ThemeSharedModule,
@ -124,10 +123,6 @@ export class ThemeSharedModule {
provide: VALIDATION_VALIDATE_ON_SUBMIT,
useValue: validation.validateOnSubmit,
},
{
provide: LOADER_DELAY,
useValue: loaderDelay,
},
],
};
}

@ -1,5 +1,4 @@
export * from './append-content.token';
export * from './http-error.token';
export * from './lazy-styles.token';
export * from './lodaer-delay.token';
export * from './suppress-unsaved-changes-warning.token';

@ -3,7 +3,6 @@ import {
DateParserFormatter,
DEFAULT_VALIDATION_BLUEPRINTS,
THEME_SHARED_ROUTE_PROVIDERS,
LOADER_DELAY,
} from '@abp/ng.theme.shared';
import { ModuleWithProviders, NgModule } from '@angular/core';
import { RouterTestingModule } from '@angular/router/testing';
@ -47,10 +46,6 @@ export class ThemeSharedTestingModule {
provide: VALIDATION_VALIDATE_ON_SUBMIT,
useValue: validation.validateOnSubmit,
},
{
provide: LOADER_DELAY,
useValue: 0,
},
],
};
}

Loading…
Cancel
Save