i am new to angular 5 . How to code a common function to show spinner for every HTTP request in angular 5.Please help me to implement this.
You can use Angular HttpInterceptor to show a spinner for all your requests, Here's a good medium article on how to implement an http interceptor
Also, you will have to create a spinner service/module and inject it in your http interceptor. Finally, in your intercept method you can use the finally
rxJs method to stop your spinner. Here's a simple implementation:
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
this.spinnerService.start();
return next.handle(req).finally(res => this.spinnerService.stop() );
}
Enjoy!
Bonus: Here's a spinner service implementation example
Source Link
Create a service
//loader.service.ts
import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class LoaderService {
public isLoading = new BehaviorSubject(false);
constructor() { }
}
Create Loader Interceptor
// loader.interceptors.ts
import { Injectable } from '@angular/core';
import {
HttpErrorResponse,
HttpResponse,
HttpRequest,
HttpHandler,
HttpEvent,
HttpInterceptor
} from '@angular/common/http';
import { Observable } from 'rxjs';
import { LoaderService } from './loader.service';
@Injectable()
export class LoaderInterceptor implements HttpInterceptor {
private requests: HttpRequest<any>[] = [];
constructor(private loaderService: LoaderService) { }
removeRequest(req: HttpRequest<any>) {
const i = this.requests.indexOf(req);
if (i >= 0) {
this.requests.splice(i, 1);
}
this.loaderService.isLoading.next(this.requests.length > 0);
}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
this.requests.push(req);
console.log("No of requests--->" + this.requests.length);
this.loaderService.isLoading.next(true);
return Observable.create(observer => {
const subscription = next.handle(req)
.subscribe(
event => {
if (event instanceof HttpResponse) {
this.removeRequest(req);
observer.next(event);
}
},
err => {
alert('error returned');
this.removeRequest(req);
observer.error(err);
},
() => {
this.removeRequest(req);
observer.complete();
});
// remove request from queue when cancelled
return () => {
this.removeRequest(req);
subscription.unsubscribe();
};
});
}
}
Now create a loader component then add in the app component
//loader.interceptor.ts
import { Component, OnInit } from '@angular/core';
import { LoaderService } from '../loader.service';
@Component({
selector: 'app-loading',
templateUrl: './loading.component.html',
styleUrls: ['./loading.component.css']
})
export class LoaderComponent implements OnInit {
loading: boolean;
constructor(private loaderService: LoaderService) {
this.loaderService.isLoading.subscribe((v) => {
console.log(v);
this.loading = v;
});
}
ngOnInit() {
}
}
The complete guide is here with a material mat-progress-spinner
usage. Very cool!
This has nothing to do with HttpClient or HTTP Requests. It is a question of how to handle asynchronous calls in general (HTTP or not).
You should have
<div class="spinner" *ngIf="loading"; else showWhenLoaded"><div>
<ng-template #showWhenLoaded>
<div>Your Content</div>
</ng-template>
and in the ts-file:
loading: boolean = true;
methodToTriggerRequest() {
this.loading = true;
this.http.get(...).subscribe(response => {
if (resposnseNotAnError(response)) {this.loading = false}
})
}
You can create a service and then publish events to it while in the root level of the application subscribed to it. Let me explain.
broadcast.service.ts (this is your service)
import { Injectable } from '@angular/core';
import { Subject } from 'rxjs/Subject';
import { Subscription } from 'rxjs/Subscription';
import 'rxjs/add/operator/filter'
import 'rxjs/add/operator/map'
/**
* This class acting as event service bus for the entire app.
* No Need to register the class as this registered in the root level.
* Can just inject to componets.
*/
@Injectable()
export class BroadcastService {
private _handler: Subject<Message> = new Subject<Message>();
broadcast(type: string, payload: any = null) {
this._handler.next({ type, payload });
}
subscribe(type: string, callback: (payload: any) => void): Subscription {
return this._handler
.filter(message => message.type === type)
.map(message => message.payload)
.subscribe(callback);
}
}
interface Message {
type: string;
payload: any;
}
Then in you can publish and subscribe to events like this:
your service level:
this.broadcastService.broadcast('PROGRESS_START');
In your app component level:
this.broadcastService.subscribe('PROGRESS_START', ()=>{
//hit when you start http call
this.myLoader = true;
});
finally in app.component.html:
<div *ngIf="myLoader">
<!--YOUR LOADER SHOULD GO HERE-->
</div>
<router-outlet></router-outlet>
This is very generic loosely coupled way to implement any loader you want.
Here I have written some steps to create your own custom progressbar.
Create a component
import { Component, Input } from '@angular/core'; @Component({ selector: 'app-sw-progressbar', template:
<div *ngIf="loading"> <mat-progress-bar style="position:absolute; left:15px; width: calc(100% - 30px); height:5px;" color="primary" mode="indeterminate"> </mat-progress-bar> </div>
, }) export class SWProgresbarComponent { @Input() loading = false; constructor() { } }
2 Import component in module:-
import { SWProgresbarComponent } from '../sw-progressbar.component';
@NgModule({
declarations: [SWProgresbarComponent],
imports: [
CommonModule,
MaterialModule,
RouterModule
],
exports: [SWProgresbarComponent,
RouterModule
]
})
export class SharedModule { }
How to use it?
Here loading would be true in the component where you want to show. Add this below code in the component where you want to show the loader.
<app-sw-progressbar [loading]="loading"></app-sw-progressbar>
For spinner replace mat-progress-bar by mat-progress-spinner.
Create interceptor service
import { Injectable } from '@angular/core';
import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';
import { Observable } from 'rxjs';
import { finalize } from 'rxjs/operators';
import { NgxSpinnerService } from 'ngx-spinner';
@Injectable()
export class SpinnerInterceptor implements HttpInterceptor {
constructor(private spinner: NgxSpinnerService) { }
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
this.spinner.show();
return next.handle(req).pipe(
finalize(() => this.spinner.hide())
);
}
}
Provide this service in app module
import { HTTP_INTERCEPTORS } from '@angular/common/http';
....
@NgModule({
providers: [
{ provide: HTTP_INTERCEPTORS, useClass: SpinnerInterceptor, multi: true }
]
})
Last in app.component inside the router-outlet add your spinner tags
<router-outlet>
<ngx-spinner bdColor="rgba(0,0,0,0.8)"
size="medium"
color="#fff"
type="timer">
<p style="color: #fff"> Loading... </p>
</ngx-spinner>
</router-outlet>
As you can see I am using NgxSpinner but this should make no difference if you're using custom spinner, you just need to create service to show and hide the spinner and inject this service in the spinner interceptor.
In Angular's interceptor I have used "do" operator.
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
// handler for both success and fail response
const onReqFinish = (event: HttpEvent<any>) => {
if (event.type === 4) {
this.onXhrFinish();
}
};
this.onXhrStart();
return next.handle(req)
.do(
onReqFinish,
onReqFinish
);
}
onXhrStart function shows a loader and onXhrFinish hides it.
Entire, working source code and demo are here
© 2022 - 2024 — McMap. All rights reserved.