Skip to content

Commit 79d5fe5

Browse files
committed
Replace custom http interceptor by the one provided from angular 4.3
1 parent 50afade commit 79d5fe5

File tree

5 files changed

+229
-33
lines changed

5 files changed

+229
-33
lines changed

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

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,31 @@ import { SpinnerComponent } from './spinner/spinner.component';
1212
import { HttpInterceptorServiceFactoryProvider } from './http-interceptor.service';
1313
import { CommonModule } from '@angular/common';
1414
import { HttpModule } from '@angular/http';
15+
import { PendingInterceptorService, PendingInterceptorServiceFactoryProvider } from './pending-interceptor.service';
16+
import { HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http';
17+
18+
const PendingInterceptorServiceExistingProvider = {
19+
provide: HTTP_INTERCEPTORS,
20+
useExisting: PendingInterceptorService,
21+
multi: true
22+
};
1523

1624
@NgModule({
1725
declarations: [
1826
SpinnerComponent,
1927
],
2028
imports: [
2129
CommonModule,
22-
HttpModule
30+
HttpModule,
31+
HttpClientModule
2332
],
2433
exports: [
2534
SpinnerComponent,
2635
],
2736
providers: [
2837
HttpInterceptorServiceFactoryProvider,
38+
PendingInterceptorServiceExistingProvider,
39+
PendingInterceptorServiceFactoryProvider
2940
]
3041
})
3142
export class NgHttpLoaderModule {
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
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 { async, inject, TestBed } from '@angular/core/testing';
11+
import { PendingInterceptorService } from './pending-interceptor.service';
12+
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
13+
import { HTTP_INTERCEPTORS, HttpClient } from '@angular/common/http';
14+
import { Observable } from 'rxjs/Observable';
15+
16+
describe('PendingInterceptorService', () => {
17+
18+
const PendingInterceptorServiceExistingProvider = {
19+
provide: HTTP_INTERCEPTORS,
20+
useExisting: PendingInterceptorService,
21+
multi: true
22+
};
23+
24+
beforeEach(() => {
25+
TestBed.configureTestingModule({
26+
imports: [HttpClientTestingModule],
27+
providers: [PendingInterceptorService, PendingInterceptorServiceExistingProvider]
28+
});
29+
});
30+
31+
it('should be created', inject([PendingInterceptorService], (service: PendingInterceptorService) => {
32+
expect(service).toBeTruthy();
33+
}));
34+
35+
it('should be aware of the pending http requests',
36+
inject(
37+
[PendingInterceptorService, HttpClient, HttpTestingController],
38+
(service: PendingInterceptorService, http: HttpClient, httpMock: HttpTestingController) => {
39+
40+
function runQuery(url: string): Observable<any> {
41+
return http.get(url);
42+
}
43+
44+
Observable.forkJoin([runQuery('/fake'), runQuery('/fake2')]).subscribe();
45+
46+
const firstRequest = httpMock.expectOne('/fake');
47+
const secondRequest = httpMock.expectOne('/fake2');
48+
49+
expect(service.pendingRequests).toBe(2);
50+
firstRequest.flush({});
51+
52+
expect(service.pendingRequests).toBe(1);
53+
secondRequest.flush({});
54+
55+
expect(service.pendingRequests).toBe(0);
56+
57+
httpMock.verify();
58+
})
59+
);
60+
61+
it('should correctly notify the pendingRequestsStatus observable', async(
62+
inject(
63+
[PendingInterceptorService, HttpClient, HttpTestingController],
64+
(service: PendingInterceptorService, http: HttpClient, httpMock: HttpTestingController) => {
65+
const pendingRequestsStatus = service.pendingRequestsStatus;
66+
67+
pendingRequestsStatus
68+
.subscribe(
69+
next => expect(next).toBeTruthy(),
70+
error => expect(1).toBe(2)
71+
);
72+
73+
http.get('/fake').subscribe();
74+
httpMock.expectOne('/fake');
75+
})
76+
));
77+
78+
it('should fail correctly',
79+
inject(
80+
[PendingInterceptorService, HttpClient, HttpTestingController],
81+
(service: PendingInterceptorService, http: HttpClient, httpMock: HttpTestingController) => {
82+
83+
const statusText = 'NOT FOUND';
84+
85+
http.get('/fake').subscribe(
86+
next => expect(1).toBe(2),
87+
(error: Response) => expect(error.statusText).toBe(statusText)
88+
);
89+
90+
const testRequest = httpMock.expectOne('/fake');
91+
testRequest.flush({}, {
92+
'headers': {
93+
'name': 'useless-header'
94+
},
95+
'status': 404,
96+
'statusText': statusText
97+
});
98+
httpMock.verify();
99+
})
100+
);
101+
});
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
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 { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';
12+
import { Observable } from 'rxjs/Observable';
13+
import { Subject } from 'rxjs/Subject';
14+
15+
@Injectable()
16+
export class PendingInterceptorService implements HttpInterceptor {
17+
private _pendingRequests = 0;
18+
private _pendingRequestsStatus: Subject<boolean> = new Subject<boolean>();
19+
20+
get pendingRequestsStatus(): Observable<boolean> {
21+
return this._pendingRequestsStatus.asObservable();
22+
}
23+
24+
get pendingRequests(): number {
25+
return this._pendingRequests;
26+
}
27+
28+
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
29+
this._pendingRequests++;
30+
31+
if (1 === this._pendingRequests) {
32+
this._pendingRequestsStatus.next(true);
33+
}
34+
35+
return next.handle(req).map(event => {
36+
return event;
37+
})
38+
.catch(error => {
39+
return Observable.throw(error);
40+
})
41+
.finally(() => {
42+
this._pendingRequests--;
43+
44+
if (0 === this._pendingRequests) {
45+
this._pendingRequestsStatus.next(false);
46+
}
47+
});
48+
}
49+
}
50+
51+
export function PendingInterceptorServiceFactory() {
52+
return new PendingInterceptorService();
53+
}
54+
55+
export let PendingInterceptorServiceFactoryProvider = {
56+
provide: PendingInterceptorService,
57+
useFactory: PendingInterceptorServiceFactory
58+
};

src/app/spinner/spinner.component.spec.ts

Lines changed: 41 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,15 @@
99

1010
import { async, ComponentFixture, inject, TestBed } from '@angular/core/testing';
1111
import { SpinnerComponent } from './spinner.component';
12-
import { HttpModule, RequestOptions, Response, ResponseOptions } from '@angular/http';
12+
import { HttpModule, RequestOptions } from '@angular/http';
1313
import { By } from '@angular/platform-browser';
1414
import { Spinkit } from '../spinkits';
15-
import { MockBackend, MockConnection } from '@angular/http/testing';
15+
import { MockBackend } from '@angular/http/testing';
1616
import { HttpInterceptorService } from '../http-interceptor.service';
1717
import { Observable } from 'rxjs/Observable';
18+
import { PendingInterceptorService } from '../pending-interceptor.service';
19+
import { HTTP_INTERCEPTORS, HttpClient } from '@angular/common/http';
20+
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
1821

1922
describe('SpinnerComponent', () => {
2023
let component: SpinnerComponent;
@@ -30,11 +33,22 @@ describe('SpinnerComponent', () => {
3033
deps: [MockBackend, RequestOptions]
3134
};
3235

36+
const PendingInterceptorServiceExistingProvider = {
37+
provide: HTTP_INTERCEPTORS,
38+
useExisting: PendingInterceptorService,
39+
multi: true
40+
};
41+
3342
beforeEach(async(() => {
3443
TestBed.configureTestingModule({
3544
declarations: [SpinnerComponent],
36-
providers: [MockBackend, HttpInterceptorServiceFactoryProvider],
37-
imports: [HttpModule]
45+
providers: [
46+
MockBackend,
47+
PendingInterceptorService,
48+
PendingInterceptorServiceExistingProvider,
49+
HttpInterceptorServiceFactoryProvider
50+
],
51+
imports: [HttpModule, HttpClientTestingModule]
3852
})
3953
.compileComponents();
4054
}));
@@ -108,40 +122,37 @@ describe('SpinnerComponent', () => {
108122
});
109123

110124
it('should show and hide the spinner according to the pending http requests',
111-
inject([HttpInterceptorService, MockBackend], (service: HttpInterceptorService, backend: MockBackend) => {
125+
inject(
126+
[PendingInterceptorService, HttpClient, HttpTestingController],
127+
(service: PendingInterceptorService, http: HttpClient, httpMock: HttpTestingController) => {
112128

113-
const connections: MockConnection[] = [],
114-
responseMock = {key: 'value'},
115-
mockResponse: Response = new Response(new ResponseOptions({body: responseMock, status: 200}));
129+
function runQuery(url: string): Observable<any> {
130+
return http.get(url);
131+
}
116132

117-
function runQuery(url: string): Observable<Response> {
118-
return service.get(url);
119-
}
133+
Observable.forkJoin([runQuery('/fake'), runQuery('/fake2')]).subscribe();
120134

121-
backend.connections.subscribe((c: MockConnection) => connections.push(c));
122-
Observable.forkJoin([runQuery('http://www.fake.url'), runQuery('http://www2.fake.url')]).subscribe();
135+
const firstRequest = httpMock.expectOne('/fake');
136+
const secondRequest = httpMock.expectOne('/fake2');
123137

124-
expect(component.isSpinnerVisible).toBeTruthy();
138+
expect(component.isSpinnerVisible).toBeTruthy();
125139

126-
connections[0].mockRespond(mockResponse);
127-
expect(component.isSpinnerVisible).toBeTruthy();
140+
firstRequest.flush({});
141+
expect(component.isSpinnerVisible).toBeTruthy();
128142

129-
connections[1].mockRespond(mockResponse);
130-
expect(component.isSpinnerVisible).toBeFalsy();
131-
})
143+
secondRequest.flush({});
144+
expect(component.isSpinnerVisible).toBeFalsy();
145+
})
132146
);
133147

134148
it('should hide and show a the spinner for a single http request',
135-
inject([HttpInterceptorService, MockBackend], (service: HttpInterceptorService, backend: MockBackend) => {
136-
let connection: MockConnection;
137-
const responseMock = {key: 'value'},
138-
mockResponse: Response = new Response(new ResponseOptions({body: responseMock, status: 200}));
139-
140-
backend.connections.subscribe((c: MockConnection) => connection = c);
141-
service.get('http://www.fake.url').subscribe();
142-
expect(component.isSpinnerVisible).toBeTruthy();
143-
connection.mockRespond(mockResponse);
144-
expect(component.isSpinnerVisible).toBeFalsy();
145-
})
149+
inject(
150+
[PendingInterceptorService, HttpClient, HttpTestingController],
151+
(service: PendingInterceptorService, http: HttpClient, httpMock: HttpTestingController) => {
152+
http.get('/fake').subscribe();
153+
expect(component.isSpinnerVisible).toBeTruthy();
154+
httpMock.expectOne('/fake').flush({});
155+
expect(component.isSpinnerVisible).toBeFalsy();
156+
})
146157
);
147158
});

src/app/spinner/spinner.component.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { Component, Input, OnDestroy } from '@angular/core';
1111
import { HttpInterceptorService } from '../http-interceptor.service';
1212
import { Subscription } from 'rxjs/Rx';
1313
import { Spinkit } from '../spinkits';
14+
import { PendingInterceptorService } from '../pending-interceptor.service';
1415

1516
@Component({
1617
selector: 'spinner',
@@ -29,22 +30,36 @@ import { Spinkit } from '../spinkits';
2930
})
3031
export class SpinnerComponent implements OnDestroy {
3132
public isSpinnerVisible: boolean;
33+
private oldSubscription: Subscription;
3234
private subscription: Subscription;
3335
public Spinkit = Spinkit;
3436
@Input()
3537
public backgroundColor: string;
3638
@Input()
3739
public spinner = Spinkit.skCubeGrid;
3840

39-
constructor(private http: HttpInterceptorService) {
40-
this.subscription = this.http
41+
constructor(private http: HttpInterceptorService, private pendingRequestInterceptorService: PendingInterceptorService) {
42+
this.oldSubscription = this.http
43+
.pendingRequestsStatus
44+
.subscribe(isSpinnerVisible => {
45+
if (isSpinnerVisible) {
46+
console.log(
47+
'HttpInterceptorService is deprecated and will soon be removed ' +
48+
'in favor of HttpClientModule. Please upgrade !'
49+
);
50+
}
51+
this.isSpinnerVisible = isSpinnerVisible;
52+
});
53+
54+
this.subscription = this.pendingRequestInterceptorService
4155
.pendingRequestsStatus
4256
.subscribe(isSpinnerVisible => {
4357
this.isSpinnerVisible = isSpinnerVisible;
4458
});
4559
}
4660

4761
ngOnDestroy(): void {
62+
this.oldSubscription.unsubscribe();
4863
this.subscription.unsubscribe();
4964
}
5065
}

0 commit comments

Comments
 (0)