9

I'm quite new with Angular so what I need is to show a spinner each time when a http request is done.

I have many components:

<component-one></component-one>
<component-two></component-two>
<component-three></component-three>
<component-n></component-n>

Each one has a http request that get or save some data so when the http request is made I want to show my <loading></loading> component so I suppose is not a good practice injecting my loading component in each other component with a http request. Can I add a filter or something from angular that allows me to load the <loading> component automatically in each component that has an http request?

Also when the http request is done I want to show a message like "Done" or someting.

If anyone can provide me a solution for that I'll really appreciate, ty.

Nikita
  • 300
  • 1
  • 2
  • 17
mcmwhfy
  • 1,654
  • 5
  • 36
  • 58
  • 2
    Possible duplicate of [Angular 2 loader on each http request](https://stackoverflow.com/questions/38144655/angular-2-loader-on-each-http-request) – eko Jul 26 '17 at 09:43
  • 1
    check this [link](https://rahulrsingh09.github.io/AngularConcepts/#/faq) it has a question and answer for the problem – Rahul Singh Jul 26 '17 at 09:46

5 Answers5

14

UPD: plunker. Take a look at app.ts. Sorry for having everything in a single file.

In Angular 4.3 there is a new HttpClientModule which supports interceptors. The main idea is to have something like this:

@Injectable()
export class LoadingIndicatorService {

    private _loading: boolean = false;

    get loading(): boolean {
        return this._loading;
    }

    onRequestStarted(): void {
        this._loading = true;
    }

    onRequestFinished(): void {
        this._loading = false;
    }
}

And then you just apply the logic from Christopher's answer to your HttpInterceptor.

The only thing you should be aware of are simultaneous request. This can be solved for example by generating a unique identifier to each request and storing it somewhere until the request is finished.

Then you can have a global LoadingIndicator component which injects LoadingIndicatorService.

For more details on HttpClientModule: https://medium.com/codingthesmartway-com-blog/angular-4-3-httpclient-accessing-rest-web-services-with-angular-2305b8fd654b

Vadim Khamzin
  • 367
  • 1
  • 10
  • is that available also in 4.2.2 ? – mcmwhfy Jul 26 '17 at 10:13
  • 1
    Only starting from 4.3.0. Well, it is not a major release. It should be actually backward-compatible. – Vadim Khamzin Jul 26 '17 at 10:16
  • do you have a plunker with a small example of that, only if you have developed something similar and is easy for you to create a plunker, not spending to much time. – mcmwhfy Jul 26 '17 at 10:22
  • 1
    @VadimKhamzin Thank you for the plunker link. Sadly I have a problem when I use your "LoadingIndicatorService" in my app.component that has child components. I always receive a "ExpressionChangedAfterItHasBeenCheckedError". Do you have an idea why? As far as I can see it has something to do with the property "loading" in the app.component. – Shamshiel Feb 19 '18 at 14:35
  • @Shamshiel could you provide your plunker? I was unable to reproduce your issue – Vadim Khamzin Feb 21 '18 at 10:57
  • @VadimKhamzin I created a simple Stackblitz with the problem: https://stackblitz.com/edit/angular-5b86se – Shamshiel Feb 22 '18 at 08:00
  • @VadimKhamzin Thanks! However, your plunkr example has two issues. 1) Line 61 should be `>=` instead of `!==` 2), you should run `changeDetector.detectChanges()` (import the `ChangeDetectorRef` too) after setting `this.loading` (line 97) to prevent the issue @Shamshiel has. – Devator Sep 06 '18 at 14:58
7

An http request returns an Observable that can be subscribed to.

For example, let's take the authentication of a user.

In my service:

login(email: string, password: string): Observable<User> {
    let url = this.authUrl + '/login';

    return this.http
        .post(url, JSON.stringify({ email, password }))
        .map(res => res.json());
}

I call and subscribe to this method in my component:

 this.isLoading = true;
 this.authService.login(email, password)
        .subscribe(data => {
            this.isLoading = false;
            // Do whatever you want once the user is logged in
            // Where 'data' is the User returned by our http request
         }, error => {
            // Handle errors here
         }

Note the boolean isLoading that is set to true before trying to log in our user, and to false once the authentication is succesful.

This means that you can show and hide your loading animation with this boolean, like such:

<loading-component *ngIf="isLoading"></loading-component>
Christopher
  • 1,712
  • 2
  • 21
  • 50
6

Check out this two npm packages:

  1. ngx-progressbar, which can show a waiting indicator automatically per request (see automagic loading bar), router events or whenever you need. See demo.

  2. ng-http-loader (for Angular 4.3+ only), which does the same job in a more traditional way and has fancy spinners from SpinKit.

Alex Klaus
  • 8,168
  • 8
  • 71
  • 87
2

With angular 2+, Create a reusable util component

import { Component, OnInit, Input } from '@angular/core';

@Component({
  selector: 'app-progress-mask',
  templateUrl: './progress-mask.component.html',
  styleUrls: ['./progress-mask.component.css']
})
export class ProgressMaskComponent implements OnInit {
  @Input() showProgress:boolean = false;
  constructor() { }

  ngOnInit() {
  }

}

The html part:

<div class="loadWrapper" *ngIf="showProgress">  
  <div class="loader"></div>  
</div>

the CSS part:

.loadWrapper {  
    background: rgba(0,0,0,0.3);  
    width: 100%;  
    height: 100%;  
    position: fixed;  
    top:0px;  
    left:0px;  
    z-index: 99999;  
}  
.loader {
    border: 5px solid #f3f3f3; /* Light grey */
    border-top: 5px solid #3d3e3f; /* gray */
    position: absolute;
    left: 50%;
    top: 50%;
    border-radius: 50%;
    width: 50px;
    height: 50px;
    animation: spin 2s linear infinite;
}

@keyframes spin {
    0% { transform: rotate(0deg); }
    100% { transform: rotate(360deg); }
}

Now insert this to your component

<app-progress-mask [showProgress]="showMask"></app-progress-mask>

Bind showMask of your component to the reusable util component

Sanjay Singh
  • 957
  • 10
  • 8
0

in your component.ts

public loading: boolean = false;

inside your component.html

<div *ngIf="!loading">
  <component-one></component-one>
  <component-two></component-two>
  <component-three></component-three>
  <component-n></component-n>
</div>

<loading *ngIf="loading"></loading>

Whenever you need to see the loading icon, simply set this.loading = true; and to hide it this.loading = false; This will hide the components you do not want to see while loading and display the loading icon instead

Wesley Coetzee
  • 4,768
  • 3
  • 27
  • 45