10

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.

Harish
  • 157
  • 1
  • 1
  • 12

8 Answers8

17

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

Nikita Popov
  • 896
  • 10
  • 19
Mehdi Benmoha
  • 3,694
  • 3
  • 23
  • 43
  • Probably dup of: https://stackoverflow.com/questions/49385369/angular-show-spinner-for-every-http-request-with-very-less-code-changes – Mehdi Benmoha Apr 30 '18 at 14:12
4

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() {
    }

  }
Code Spy
  • 9,626
  • 4
  • 66
  • 46
3

The complete guide is here with a material mat-progress-spinner usage. Very cool!

trueboroda
  • 2,650
  • 26
  • 24
1

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}
    })
}
Phil
  • 7,065
  • 8
  • 49
  • 91
  • 2
    In this way how many loaders are you going to introduce to the application? – Rukshan Dangalla Apr 30 '18 at 12:42
  • 1
    one for each item that needs to be fetched individually. You don't need to block the entire app with a loader if you fetch dynamic data that is used only in one part of the screen – Phil Apr 30 '18 at 12:59
0

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.

Rukshan Dangalla
  • 2,500
  • 2
  • 24
  • 29
  • He could also implement NGRX, but this is not what he asked – Phil Apr 30 '18 at 12:39
  • He asked about "common function to show spinner for every HTTP request". So in this way he can just publish progress start event from anywhere in the application. – Rukshan Dangalla Apr 30 '18 at 12:41
0

Here I have written some steps to create your own custom progressbar.

  1. 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 { }
  1. 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.

Shashikant Pandit
  • 2,752
  • 22
  • 29
0

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.

Ali Jamal
  • 619
  • 7
  • 12
-1

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