Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prepare v3.1.0 #94

Merged
merged 20 commits into from Sep 22, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
25 changes: 25 additions & 0 deletions CHANGELOG.md
@@ -1,5 +1,30 @@
# Changelog

## v3.1.0

Awesome contribution by [gnom7](https://github.com/gnom7)
- Better handling of sequential HTTP requests. Particularly when mixed with the ``minDuration`` option. See [this issue](https://github.com/mpalourdio/ng-http-loader/issues/89) for reference.

```
Min. duration time: 300ms
---0ms------------------------------200ms-------280ms----------------400ms|
----|---------------------------------------------|-----------------------|
(req1 starts and spinner shows) (req1 ends) (req2 starts) (req2 ends and spinner hides)
```

Before this, minDuration would have been applied to both HTTP requests.

- Added the ``extraDuration`` option:
- This option make the spinner visible a certain amount of time after the moment when it should have naturally been hidden. This avoids flickering when, for example, multiple HTTP requests are ran sequentially.
- See [this issue](https://github.com/mpalourdio/ng-http-loader/issues/90) for reference

```
Extra duration time: 60ms
---0ms----------200ms------260ms---- |
----|------------|----------|--------|
req starts req ends spinner hides
```

## v3.0.0

- All existing deprecations have been removed.
Expand Down
5 changes: 3 additions & 2 deletions README.md
Expand Up @@ -79,13 +79,14 @@ In your app.component.html, simply add:

## Customizing the spinner

You can customize the **background-color**, the **spinner type**, the **debounce delay** (ie. after how many milliseconds the spinner will be visible, if needed), the **minimum duration** (ie. how many milliseconds should the spinner be visible at least):
You can customize the **background-color**, the **spinner type**, the **debounce delay** (ie. after how many milliseconds the spinner will be visible, if needed), the **minimum duration** (ie. how many milliseconds should the spinner be visible at least), the **extra duration** (ie. how many extra milliseconds should the spinner be visible):
```xml
<ng-http-loader
[backgroundColor]="'#ff0000'"
[spinner]="spinkit.skWave"
[debounceDelay]="100"
[minDuration]="300">
[minDuration]="300"
[extraDuration]="300">
</ng-http-loader>
```

Expand Down
2 changes: 1 addition & 1 deletion angular.json
Expand Up @@ -11,7 +11,7 @@
"styleext": "scss"
}
},
"architect": {
"targets": {
"build": {
"builder": "@angular-devkit/build-ng-packagr:build",
"options": {
Expand Down
4 changes: 2 additions & 2 deletions package.json
@@ -1,6 +1,6 @@
{
"name": "ng-http-loader",
"version": "3.1.0-beta.0",
"version": "3.1.0",
"scripts": {
"ng": "ng",
"build": "ng build",
Expand Down Expand Up @@ -64,7 +64,7 @@
"karma-firefox-launcher": "^1.1.0",
"karma-jasmine": "~1.1.2",
"karma-jasmine-html-reporter": "^0.2.2",
"ng-packagr": "^3.0.0",
"ng-packagr": "^4.1.0",
"rxjs": "~6.2.0",
"ts-node": "~7.0.0",
"tsickle": ">=0.29.0",
Expand Down
15 changes: 7 additions & 8 deletions src/lib/components/ng-http-loader.component.ts
Expand Up @@ -58,14 +58,14 @@ export class NgHttpLoaderComponent implements OnDestroy, OnInit {
}

private initializeSubscription() {
const [showSpinner, hideSpinner] = partition((h: boolean) => h)(this.pendingInterceptorService.pendingRequestsStatus$);
const [showSpinner$, hideSpinner$] = partition((h: boolean) => h)(this.pendingInterceptorService.pendingRequestsStatus$);

this.subscriptions = merge(
this.pendingInterceptorService.pendingRequestsStatus$.pipe(
switchMap(() => showSpinner.pipe(debounce(() => timer(this.debounceDelay))))
switchMap(() => showSpinner$.pipe(debounce(() => timer(this.debounceDelay))))
),
showSpinner.pipe(
switchMap(() => hideSpinner.pipe(debounce(() => this.getHidingTimer())))
showSpinner$.pipe(
switchMap(() => hideSpinner$.pipe(debounce(() => this.getVisibilityTimer())))
),
this.spinnerVisibilityService.visibilityObservable$,
)
Expand Down Expand Up @@ -112,14 +112,13 @@ export class NgHttpLoaderComponent implements OnDestroy, OnInit {
}

private handleSpinnerVisibility(showSpinner: boolean): void {
const now = Date.now();
if (showSpinner && this.visibleUntil <= now) {
this.visibleUntil = now + this.minDuration;
if (showSpinner) {
this.visibleUntil = Date.now() + this.minDuration;
}
this.isSpinnerVisible = showSpinner;
}

private getHidingTimer(): Observable<number> {
private getVisibilityTimer(): Observable<number> {
return timer(Math.max(this.extraDuration, this.visibleUntil - Date.now()));
}
}
123 changes: 123 additions & 0 deletions src/test/components/ng-http-loader.component.spec.ts
Expand Up @@ -528,6 +528,34 @@ describe('NgHttpLoaderComponent', () => {
}
)));

it('should correctly handle the extra spinner duration for a single HTTP request', fakeAsync(inject(
[HttpClient, HttpTestingController], (http: HttpClient, httpMock: HttpTestingController) => {
component.extraDuration = 5000;
http.get('/fake').subscribe();

// the HTTP request is pending for 1 second now
tick(1000);
expect(component.isSpinnerVisible).toBeTruthy();

// the HTTP request is pending for 2 seconds now
tick(1000);
expect(component.isSpinnerVisible).toBeTruthy();

// the HTTP request is finally over, the spinner is still visible
httpMock.expectOne('/fake').flush({});
tick();
expect(component.isSpinnerVisible).toBeTruthy();

// 4 seconds after the HTTP request is over, the spinner is still visible
tick(4000);
expect(component.isSpinnerVisible).toBeTruthy();

// the spinner is not visible anymore after 5 seconds
tick(1000);
expect(component.isSpinnerVisible).toBeFalsy();
}
)));

it('should correctly handle the minimum spinner duration for multiple HTTP requests', fakeAsync(inject(
[HttpClient, HttpTestingController], (http: HttpClient, httpMock: HttpTestingController) => {
component.minDuration = 5000;
Expand Down Expand Up @@ -573,6 +601,55 @@ describe('NgHttpLoaderComponent', () => {
}
)));

it('should correctly handle the extra spinner duration for multiple HTTP requests', fakeAsync(inject(
[HttpClient, HttpTestingController], (http: HttpClient, httpMock: HttpTestingController) => {
component.extraDuration = 5000;

function runQuery(url: string): Observable<any> {
return http.get(url);
}

forkJoin([runQuery('/fake'), runQuery('/fake2')]).subscribe();

const firstRequest = httpMock.expectOne('/fake');
const secondRequest = httpMock.expectOne('/fake2');

// the HTTP requests are pending for 1 second now
tick(1000);
expect(component.isSpinnerVisible).toBeTruthy();

// the HTTP requests are pending for 2 seconds now
tick(1000);
expect(component.isSpinnerVisible).toBeTruthy();

// the first HTTP request is finally over, the spinner is still visible
firstRequest.flush({});
tick();
expect(component.isSpinnerVisible).toBeTruthy();

// the second HTTP request is still pending after 3 seconds
tick(1000);
expect(component.isSpinnerVisible).toBeTruthy();

// the second HTTP request is still pending after 4 seconds
tick(1000);
expect(component.isSpinnerVisible).toBeTruthy();

// the second HTTP request is finally over too, the spinner is still visible
secondRequest.flush({});
tick();
expect(component.isSpinnerVisible).toBeTruthy();

// After 4 seconds, the spinner is still visible
tick(4000);
expect(component.isSpinnerVisible).toBeTruthy();

// After 5 seconds, the spinner is hidden
tick(1000);
expect(component.isSpinnerVisible).toBeFalsy();
}
)));

it('should correctly handle the minimum spinner duration for multiple HTTP requests ran one after the others', fakeAsync(inject(
[HttpClient, HttpTestingController], (http: HttpClient, httpMock: HttpTestingController) => {
component.minDuration = 2000;
Expand Down Expand Up @@ -673,6 +750,17 @@ describe('NgHttpLoaderComponent', () => {
}
));

it('should be possible to set the extra duration without side effect on manual show/hide', inject(
[SpinnerVisibilityService], (spinner: SpinnerVisibilityService) => {
component.extraDuration = 10000;
spinner.show();
expect(component.isSpinnerVisible).toBeTruthy();

spinner.hide();
expect(component.isSpinnerVisible).toBeFalsy();
}
));

it('should be possible to mix debounce delay and minimum duration', fakeAsync(inject(
[HttpClient, HttpTestingController], (http: HttpClient, httpMock: HttpTestingController) => {
// the spinner should not be visible the first second, then visible for 5 seconds
Expand Down Expand Up @@ -702,6 +790,41 @@ describe('NgHttpLoaderComponent', () => {
tick(2999);
expect(component.isSpinnerVisible).toBeTruthy();

// after 6 seconds (1s for debounce + 5s extra. duration), the spinner is hidden
tick(1);
expect(component.isSpinnerVisible).toBeFalsy();
}
)));

it('should be possible to mix debounce delay and extra duration', fakeAsync(inject(
[HttpClient, HttpTestingController], (http: HttpClient, httpMock: HttpTestingController) => {
// the spinner should not be visible the first second, then visible for 5 seconds
component.extraDuration = 5000;
component.debounceDelay = 1000;

http.get('/fake').subscribe();

// the HTTP request is pending for 0,5 second now - spinner not visible because debounce
tick(500);
expect(component.isSpinnerVisible).toBeFalsy();

// the HTTP request is pending for 1 second now - spinner visible
tick(500);
expect(component.isSpinnerVisible).toBeTruthy();

// the HTTP request is finally over, the spinner is still visible
httpMock.expectOne('/fake').flush({});
tick();
expect(component.isSpinnerVisible).toBeTruthy();

// after 3 seconds, the spinner is still visible
tick(2000);
expect(component.isSpinnerVisible).toBeTruthy();

// after 5,999 seconds, the spinner is still visible
tick(2999);
expect(component.isSpinnerVisible).toBeTruthy();

// after 6 seconds (1s for debounce + 5s min. duration), the spinner is hidden
tick(1);
expect(component.isSpinnerVisible).toBeFalsy();
Expand Down
4 changes: 2 additions & 2 deletions tsconfig.lib.json
Expand Up @@ -10,6 +10,7 @@
"noImplicitAny": true,
"noImplicitReturns": true,
"types": [],
"baseUrl": "./",
"lib": [
"dom",
"es2015"
Expand All @@ -21,8 +22,7 @@
"strictMetadataEmit": true,
"fullTemplateTypeCheck": true,
"strictInjectionParameters": true,
"flatModuleId": "AUTOGENERATED",
"flatModuleOutFile": "AUTOGENERATED"
"enableResourceInlining": true
},
"exclude": [
"./src/test.ts",
Expand Down