Im new to Angular2 and I was wondering if there is any way to show an activity indicator for every HTTP request and hide views until completed?
One way is to write an interceptor for Angular2 Http. By creating your own http instance you can swap that in when you are bootstrapping your application by use of the "provide" method. Once this is done a PubSub service can be created to publish and subscribe to these events from your Http interceptor and emit before and after events on every request made.
A live example can be seen on Plunker
The Interceptor:
import {Injectable} from 'angular2/core';
import {HTTP_PROVIDERS, Http, Request, RequestOptionsArgs, Response, XHRBackend, RequestOptions, ConnectionBackend, Headers} from 'angular2/http';
import 'rxjs/Rx';
import {PubSubService} from './pubsubService';
@Injectable()
export class CustomHttp extends Http {
_pubsub: PubSubService
constructor(backend: ConnectionBackend, defaultOptions: RequestOptions, pubsub: PubSubService) {
super(backend, defaultOptions);
this._pubsub = pubsub;
}
request(url: string | Request, options?: RequestOptionsArgs): Observable<Response> {
return this.intercept(super.request(url, options));
}
get(url: string, options?: RequestOptionsArgs): Observable<Response> {
return this.intercept(super.get(url,options));
}
post(url: string, body: string, options?: RequestOptionsArgs): Observable<Response> {
return this.intercept(super.post(url, body, this.getRequestOptionArgs(options)));
}
put(url: string, body: string, options?: RequestOptionsArgs): Observable<Response> {
return this.intercept(super.put(url, body, this.getRequestOptionArgs(options)));
}
delete(url: string, options?: RequestOptionsArgs): Observable<Response> {
return this.intercept(super.delete(url, options));
}
getRequestOptionArgs(options?: RequestOptionsArgs) : RequestOptionsArgs {
if (options == null) {
options = new RequestOptions();
}
if (options.headers == null) {
options.headers = new Headers();
}
options.headers.append('Content-Type', 'application/json');
return options;
}
intercept(observable: Observable<Response>): Observable<Response> {
this._pubsub.beforeRequest.emit("beforeRequestEvent");
//this will force the call to be made immediately..
observable.subscribe(
null,
null,
() => this._pubsub.afterRequest.emit("afterRequestEvent");
);
return observable
}
}
The Emitters
import {Subject } from 'rxjs/Subject';
export class RequestEventEmitter extends Subject<String>{
constructor() {
super();
}
emit(value) { super.next(value); }
}
export class ResponseEventEmitter extends Subject<String>{
constructor() {
super();
}
emit(value) { super.next(value); }
}
The PubSubService
import {Injectable} from 'angular2/core';
import {RequestEventEmitter, ResponseEventEmitter} from './emitter';
@Injectable()
export class PubSubService{
beforeRequest:RequestEventEmitter;
afterRequest:ResponseEventEmitter;
constructor(){
this.beforeRequest = new RequestEventEmitter();
this.afterRequest = new ResponseEventEmitter();
}
}
Bootstrapping the App
//main entry point
import {bootstrap} from 'angular2/platform/browser';
import {provide} from 'angular2/core';
import {Http, HTTP_PROVIDERS, XHRBackend, RequestOptions} from 'angular2/http';
import {HelloWorldComponent} from './hello_world';
import {CustomHttp} from './customhttp';
import {PubSubService} from './pubsubService'
bootstrap(HelloWorldComponent, [HTTP_PROVIDERS,PubSubService,
provide(Http, {
useFactory: (backend: XHRBackend, defaultOptions: RequestOptions, pubsub: PubSubService)
=> new CustomHttp(backend, defaultOptions, pubsub),
deps: [XHRBackend, RequestOptions, PubSubService]
})
]).catch(err => console.error(err));
Now in your loading component its as easy as subscribing to the events and setting a property to show or not
export class LoaderComponent implements OnInit {
showLoader = false;
_pubsub:PubSubService;
constructor(pubsub: PubSubService) {
this._pubsub = pubsub;
}
ngOnInit() {
this._pubsub.beforeRequest.subscribe(data => this.showLoader = true);
this._pubsub.afterRequest.subscribe(data => this.showLoader = false);
}
}
While this ends up being a bit more code, if you are looking to be notified on every request in your application this would do it. One thing to note with the interceptor is since a subscribe is being done for every request immediately all requests will be executed, which may not be what you need in particular cases. A solution to that is to support the regular Angular2 Http and use the CustomHttp as a second option that could be injected where needed. I would think in most cases immediate subscription would work fine. I would love to hear examples of when it would not.
Yes, you need to handle that for each View :
The Service :
@Injectable()
export class DataService {
constructor(private http: Http) { }
getData() {
return this.http.get('http://jsonplaceholder.typicode.com/posts/2');
}
}
The Component :
@Component({
selector: 'my-app',
template : `
<div *ngIf="loading == true" class="loader">Loading..</div>
<div *ngIf="loading == false">Content.... a lot of content <br> more content</div>`
})
export class App {
loading: boolean;
constructor(private dataService: DataService) { }
ngOnInit() {
// Start loading Data from the Server
this.loading = true;
this.dataService.getData().delay(1500).subscribe(
requestData => {
// Data loading is Done
this.loading = false;
console.log('AppComponent', requestData);
}
}
}
A working example can be found here : http://plnkr.co/edit/HDEDDLOeiHEDd7VQaev5?p=preview
In addition @tibbus response
This is better to set "isLoading" type of Number and keep it in service.
with boolean:
request 1 starts -> spinner on -> request 2 starts -> request 1 ends -> spinner off -> request 2 ends
with number:
request 1 starts -> spinner on -> request 2 starts -> request 1 ends -> request 2 ends -> spinner off
Service
@Injectable()
export class DataService {
constructor(private http: Http) { }
private set fetchCounter(v:number) {
this._fetchCounter = v;
this.isLoadingSource.next(this._fetchCounter > 0)
}
private get fetchCounter() { return this._fetchCounter };
private _fetchCounter:number = 0;
private isLoadingSource = new Subject<boolean>();
public isLoading = this.isLoadingSource.asObservable();
public getData() {
this.fetchCounter++;
return this.http.get('http://jsonplaceholder.typicode.com/posts/2')
.map(r => {
this.fetchCounter--;
return r;
});
}
}
You just need to subscribe to isLoading from any of your components.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With