Skip to content

Commit c330adb

Browse files
authored
Merge pull request #54 from mpalourdio/show-hide
Show / hide the spinner manually, Fixes #22 #33 #52 #53
2 parents a13cfbb + 5213ebe commit c330adb

File tree

9 files changed

+177
-83
lines changed

9 files changed

+177
-83
lines changed

CHANGELOG.MD

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Changelog
22

3+
## v0.8.0
4+
5+
This release adds the ``SpinnerVisibilityService``, a simple injectable service that allows you to manually show/hide the spinner.
6+
7+
See the [Manually show and hide the spinner](https://github.com/mpalourdio/ng-http-loader#manually-show-and-hide-the-spinner) section.
8+
39
## v0.7.1
410

511
This release is a bug fix release. It slightly improves the behavior of the ``filteredUrlPatterns`` so this parameter takes care of query strings too.
@@ -8,7 +14,7 @@ This release is a bug fix release. It slightly improves the behavior of the ``fi
814

915
This release adds the ``entryComponent`` property. It allows to specify your own component instead of the built-in ones. It uses the [NgComponentOutlet](https://angular.io/api/common/NgComponentOutlet) feature.
1016

11-
See [Defining your own spinner](https://github.com/mpalourdio/ng-http-loader#defining-your-own-spinner) section.
17+
See the [Defining your own spinner](https://github.com/mpalourdio/ng-http-loader#defining-your-own-spinner) section.
1218

1319
## v0.6.0
1420

README.MD

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,29 @@ You can also filter the http requests that shouldn't be caught by the intercepto
158158
<spinner [filteredUrlPatterns]="['\\d', '[a-zA-Z]', 'my-api']"></spinner>
159159
```
160160

161+
## Manually show and hide the spinner
162+
163+
You can manually show and hide the spinner component if needed. You must use the ``SpinnerVisibilityService`` for this purpose.
164+
165+
```typescript
166+
import { SpinnerVisibilityService } from 'ng-http-loader/services/spinner-visibility.service';
167+
168+
@Component({
169+
selector: 'my-component',
170+
templateUrl: './my.component.html',
171+
styleUrls: ['./my.component.css'],
172+
})
173+
export class MyComponent {
174+
175+
constructor(private visibilityService: SpinnerVisibilityService) {
176+
// show the spinner
177+
visibilityService.visibilitySubject.next(true);
178+
// hide the spinner
179+
visibilityService.visibilitySubject.next(false);
180+
}
181+
}
182+
```
183+
161184
## Misc
162185

163186
Each Spinkit component defined in [SPINKIT_COMPONENTS](src/spinkits.ts#L30) can be used independently.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "ng-http-loader",
3-
"version": "0.7.1",
3+
"version": "0.8.0",
44
"scripts": {
55
"prepare-deploy": "gulp inline-templates && gulp clean-dist && ngc -p tsconfig.ngc.json && gulp clean-tmp && gulp copy-all",
66
"test": "ng test --watch false"

src/components/spinner/spinner.component.ts

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { PendingInterceptorService } from '../../services/pending-interceptor.se
1414
import { timer } from 'rxjs/observable/timer';
1515
import { Observable } from 'rxjs/Observable';
1616
import { debounce } from 'rxjs/operators';
17+
import { SpinnerVisibilityService } from '../../services/spinner-visibility.service';
1718

1819
@Component({
1920
selector: 'spinner',
@@ -22,9 +23,10 @@ import { debounce } from 'rxjs/operators';
2223
})
2324
export class SpinnerComponent implements OnDestroy, OnInit {
2425
public isSpinnerVisible: boolean;
25-
private subscription: Subscription;
26-
public Spinkit = Spinkit;
26+
private pendingSubscription: Subscription;
27+
private visibilitySubscription: Subscription;
2728

29+
public Spinkit = Spinkit;
2830
@Input()
2931
public backgroundColor: string;
3032
@Input()
@@ -36,13 +38,19 @@ export class SpinnerComponent implements OnDestroy, OnInit {
3638
@Input()
3739
public entryComponent: any = null;
3840

39-
constructor(private pendingRequestInterceptorService: PendingInterceptorService) {
40-
this.subscription = this.pendingRequestInterceptorService
41+
constructor(private pendingInterceptorService: PendingInterceptorService, private spinnerVisibilityService: SpinnerVisibilityService) {
42+
this.pendingSubscription = this.pendingInterceptorService
4143
.pendingRequestsStatus
4244
.pipe(debounce(this.handleDebounce.bind(this)))
43-
.subscribe(hasPendingRequests => {
44-
this.isSpinnerVisible = hasPendingRequests;
45-
});
45+
.subscribe(this.handleSpinnerVisibility().bind(this));
46+
47+
this.visibilitySubscription = this.spinnerVisibilityService
48+
.visibilitySubject
49+
.subscribe(this.handleSpinnerVisibility().bind(this));
50+
}
51+
52+
private handleSpinnerVisibility(): (v: boolean) => void {
53+
return v => this.isSpinnerVisible = v;
4654
}
4755

4856
ngOnInit(): void {
@@ -54,7 +62,7 @@ export class SpinnerComponent implements OnDestroy, OnInit {
5462

5563
if (!!this.filteredUrlPatterns.length) {
5664
this.filteredUrlPatterns.forEach(e => {
57-
this.pendingRequestInterceptorService.filteredUrlPatterns.push(new RegExp(e));
65+
this.pendingInterceptorService.filteredUrlPatterns.push(new RegExp(e));
5866
});
5967
}
6068
}
@@ -65,9 +73,9 @@ export class SpinnerComponent implements OnDestroy, OnInit {
6573
}
6674
}
6775

68-
6976
ngOnDestroy(): void {
70-
this.subscription.unsubscribe();
77+
this.pendingSubscription.unsubscribe();
78+
this.visibilitySubscription.unsubscribe();
7179
}
7280

7381
private handleDebounce(hasPendingRequests: boolean): Observable<number> {

src/services/ng-http-loader-services.module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { NgModule } from '@angular/core';
1111
import { CommonModule } from '@angular/common';
1212
import { HTTP_INTERCEPTORS } from '@angular/common/http';
1313
import { PendingInterceptorService, PendingInterceptorServiceFactoryProvider } from './pending-interceptor.service';
14+
import { SpinnerVisibilityServiceFactoryProvider } from './spinner-visibility.service';
1415

1516
const PendingInterceptorServiceExistingProvider = {
1617
provide: HTTP_INTERCEPTORS,
@@ -25,6 +26,7 @@ const PendingInterceptorServiceExistingProvider = {
2526
providers: [
2627
PendingInterceptorServiceExistingProvider,
2728
PendingInterceptorServiceFactoryProvider,
29+
SpinnerVisibilityServiceFactoryProvider,
2830
],
2931
})
3032
export class NgHttpLoaderServicesModule {
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
3+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
4+
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
5+
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
6+
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
7+
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
8+
*/
9+
10+
import { Injectable } from '@angular/core';
11+
import { Subject } from 'rxjs/Subject';
12+
13+
@Injectable()
14+
export class SpinnerVisibilityService {
15+
private _visibilitySubject: Subject<boolean> = new Subject<boolean>();
16+
17+
get visibilitySubject(): Subject<boolean> {
18+
return this._visibilitySubject;
19+
}
20+
}
21+
22+
export function SpinnerVisibilityServiceFactory(): SpinnerVisibilityService {
23+
return new SpinnerVisibilityService();
24+
}
25+
26+
export let SpinnerVisibilityServiceFactoryProvider = {
27+
provide: SpinnerVisibilityService,
28+
useFactory: SpinnerVisibilityServiceFactory
29+
};

test/components/spinner/spinner.component.spec.ts

Lines changed: 31 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,11 @@ import { SpinnerComponent } from '../../../src/components/spinner/spinner.compon
1212
import { By } from '@angular/platform-browser';
1313
import { Spinkit, SPINKIT_COMPONENTS } from '../../../src/spinkits';
1414
import { Observable } from 'rxjs/Observable';
15-
import { PendingInterceptorService } from '../../../src/services/pending-interceptor.service';
1615
import { HttpClient } from '@angular/common/http';
1716
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
1817
import { NgHttpLoaderServicesModule } from '../../../src/services/ng-http-loader-services.module';
1918
import 'rxjs/add/observable/forkJoin';
19+
import { SpinnerVisibilityService } from '../../../src/services/spinner-visibility.service';
2020

2121
describe('SpinnerComponent', () => {
2222
let component: SpinnerComponent;
@@ -99,8 +99,7 @@ describe('SpinnerComponent', () => {
9999
});
100100

101101
it('should show and hide the spinner according to the pending http requests', fakeAsync(inject(
102-
[PendingInterceptorService, HttpClient, HttpTestingController],
103-
(service: PendingInterceptorService, http: HttpClient, httpMock: HttpTestingController) => {
102+
[HttpClient, HttpTestingController], (http: HttpClient, httpMock: HttpTestingController) => {
104103

105104
function runQuery(url: string): Observable<any> {
106105
return http.get(url);
@@ -125,8 +124,7 @@ describe('SpinnerComponent', () => {
125124
)));
126125

127126
it('should hide and show a the spinner for a single http request', fakeAsync(inject(
128-
[PendingInterceptorService, HttpClient, HttpTestingController],
129-
(service: PendingInterceptorService, http: HttpClient, httpMock: HttpTestingController) => {
127+
[HttpClient, HttpTestingController], (http: HttpClient, httpMock: HttpTestingController) => {
130128
http.get('/fake').subscribe();
131129

132130
tick();
@@ -139,8 +137,7 @@ describe('SpinnerComponent', () => {
139137
)));
140138

141139
it('should not show the spinner if the request is filtered', fakeAsync(inject(
142-
[PendingInterceptorService, HttpClient, HttpTestingController],
143-
(service: PendingInterceptorService, http: HttpClient, httpMock: HttpTestingController) => {
140+
[HttpClient, HttpTestingController], (http: HttpClient, httpMock: HttpTestingController) => {
144141
component.filteredUrlPatterns.push('fake');
145142
fixture.detectChanges();
146143

@@ -152,8 +149,7 @@ describe('SpinnerComponent', () => {
152149
)));
153150

154151
it('should take care of query strings in filteredUrlPatterns', fakeAsync(inject(
155-
[PendingInterceptorService, HttpClient, HttpTestingController],
156-
(service: PendingInterceptorService, http: HttpClient, httpMock: HttpTestingController) => {
152+
[HttpClient, HttpTestingController], (http: HttpClient, httpMock: HttpTestingController) => {
157153
component.filteredUrlPatterns.push('bar');
158154
fixture.detectChanges();
159155

@@ -172,8 +168,7 @@ describe('SpinnerComponent', () => {
172168
)));
173169

174170
it('should correctly filter with several requests and one pattern', fakeAsync(inject(
175-
[PendingInterceptorService, HttpClient, HttpTestingController],
176-
(service: PendingInterceptorService, http: HttpClient, httpMock: HttpTestingController) => {
171+
[HttpClient, HttpTestingController], (http: HttpClient, httpMock: HttpTestingController) => {
177172
component.filteredUrlPatterns.push('\\d');
178173
fixture.detectChanges();
179174

@@ -197,28 +192,25 @@ describe('SpinnerComponent', () => {
197192
expect(() => fixture.detectChanges()).toThrow(new Error('`filteredUrlPatterns` must be an array.'));
198193
});
199194

200-
it('should show the spinner even if the component is created after the http request is performed',
201-
fakeAsync(inject([PendingInterceptorService, HttpClient, HttpTestingController],
202-
(service: PendingInterceptorService, http: HttpClient, httpMock: HttpTestingController) => {
203-
http.get('/fake').subscribe();
195+
it('should show the spinner even if the component is created after the http request is performed', fakeAsync(inject(
196+
[HttpClient, HttpTestingController], (http: HttpClient, httpMock: HttpTestingController) => {
197+
http.get('/fake').subscribe();
204198

205-
const newFixture = TestBed.createComponent(SpinnerComponent);
206-
const newComponent = newFixture.componentInstance;
199+
const newFixture = TestBed.createComponent(SpinnerComponent);
200+
const newComponent = newFixture.componentInstance;
207201

208-
tick();
209-
expect(newComponent.isSpinnerVisible).toBeTruthy();
210-
httpMock.expectOne('/fake').flush({});
202+
tick();
203+
expect(newComponent.isSpinnerVisible).toBeTruthy();
204+
httpMock.expectOne('/fake').flush({});
211205

212-
tick();
213-
expect(newComponent.isSpinnerVisible).toBeFalsy();
214-
httpMock.verify();
215-
}
216-
))
217-
);
206+
tick();
207+
expect(newComponent.isSpinnerVisible).toBeFalsy();
208+
httpMock.verify();
209+
}
210+
)));
218211

219212
it('should correctly handle the debounce delay for a single http request', fakeAsync(inject(
220-
[PendingInterceptorService, HttpClient, HttpTestingController],
221-
(service: PendingInterceptorService, http: HttpClient, httpMock: HttpTestingController) => {
213+
[HttpClient, HttpTestingController], (http: HttpClient, httpMock: HttpTestingController) => {
222214
component.debounceDelay = 2000;
223215
http.get('/fake').subscribe();
224216

@@ -249,8 +241,7 @@ describe('SpinnerComponent', () => {
249241
)));
250242

251243
it('should correctly handle the debounce delay for multiple http requests', fakeAsync(inject(
252-
[PendingInterceptorService, HttpClient, HttpTestingController],
253-
(service: PendingInterceptorService, http: HttpClient, httpMock: HttpTestingController) => {
244+
[HttpClient, HttpTestingController], (http: HttpClient, httpMock: HttpTestingController) => {
254245
component.debounceDelay = 2000;
255246

256247
function runQuery(url: string): Observable<any> {
@@ -296,4 +287,14 @@ describe('SpinnerComponent', () => {
296287
discardPeriodicTasks();
297288
}
298289
)));
290+
291+
it('should be possible to manually show/hide the spinner', inject(
292+
[SpinnerVisibilityService], (visibilityService: SpinnerVisibilityService) => {
293+
visibilityService.visibilitySubject.next(true);
294+
expect(component.isSpinnerVisible).toBeTruthy();
295+
296+
visibilityService.visibilitySubject.next(false);
297+
expect(component.isSpinnerVisible).toBeFalsy();
298+
}
299+
));
299300
});

0 commit comments

Comments
 (0)