9

What i am trying to do is:
I want to use the spinner whenever a http request accurs. In other words i want user to see a loading screen whenever a http request happens in my app.component.
My spinner.component and spinner-service files are same with the answer in this question.
And my app.component's component is

@Component({
    selector: 'todoApi',
    template: `
        <div class="foo">
            <spinner-component></spinner-component>
            <h1>Internship Project</h1>
            <a [routerLink]="['Dashboard']">Dashboard</a>
            <a [routerLink]="['Tasks']">List</a>
            <router-outlet></router-outlet>
        <div>
    `,
    directives: [ROUTER_DIRECTIVES,SpinnerComponent],
    providers: [
        ROUTER_PROVIDERS,
    ]
})

@RouteConfig([
    {
        path: '/dashboard',
        name: 'Dashboard',
        component: DashboardComponent,
        useAsDefault: true
    },{
        path: '/tasks',
        name: 'Tasks',
        component: TaskComponent
    },{
        path: '/detail/:id',
        name: 'TaskDetail',
        component: TaskDetailComponent
    },
])

To conclue , whenever a http request occurs in one of these routes, i want to show the spinner to user. I know this has been a bad question , but i am new to angular 2 and i would realy be grateful if anyone could help me with that.
Thanks alot!
Edit!:
Solution with Günther's answer: I wrapped my http and spinner-service into a HttpClient component and used it instead of regular http module. Here is my HttpClient component:

import { Injectable } from '@angular/core';
import { Http, Headers } from '@angular/http';
import { SpinnerService} from './spinner-service';

@Injectable()
export class HttpClient {
  constructor(
      private http: Http,
      public spinner: SpinnerService
    ){

  }

  createAuthorizationHeader(headers:Headers) {
    headers.append('Authorization', 'Basic ' + btoa('username:password')); 
  }

  get(url) {
    this.spinner.start();
    let headers = new Headers();
    this.createAuthorizationHeader(headers);
    return this.http.get(url, { headers: headers }).do(data=> {this.spinner.stop()});
  }

  post(url, data) {
    this.spinner.start();
    let headers = new Headers();
    this.createAuthorizationHeader(headers);
    return this.http.post(url, data, { headers: headers }).do(data=> {this.spinner.stop()});
  }
}
Community
  • 1
  • 1
ozata
  • 542
  • 1
  • 5
  • 16
  • Maybe you create custom service to perform http requests extending the HttpClass provided by the angular and track the `isBusy` state in your custom service upon each request . – Lekhnath Jul 01 '16 at 11:52

4 Answers4

7

Use a service that is shared between Http (there are answers already how about wrapping Http in your own class) and the <spinner-component>. See also https://angular.io/docs/ts/latest/cookbook/component-communication.html

In the shared service maintain a counter of started (increase) and completed/failed HTTP requests and notify the <spinner-component> every time when the counter changes from 0 to >0 or from >0 to 0 to enable or disable itself.

Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
4

Thanks for your answer Günter Zöchbauer's, An example which I built based on my needs. I did not use an HTTP wrapper which would be easier to use, however, this example works with multiple services calls based on your counter suggestion. Hope it helps someone :)

  1. Create the Loader service.

    import { Injectable } from '@angular/core';
    import { BehaviorSubject } from 'rxjs/BehaviorSubject';
    
    @Injectable()
    
    export class LoaderService {
        public loaderCounter: BehaviorSubject<number> = new BehaviorSubject<number>(0);
        displayLoader(value: boolean) {
          let counter = value ? this.loaderCounter.value + 1 : this.loaderCounter.value - 1;
          this.loaderCounter.next(counter);
        }
    }
    
  2. Include the service within the providers of your maain module file (Ex: AppModule)

  3. In your main component file (Ex: AppComponent), subscribe to the changes and reflect to the loader (in my case it's a seperate component).

    //Imports
    import { Subscription } from 'rxjs/Subscription';
    import { LoaderService } from './core/loader.service';
    ..
    @Component({
      selector: 'my-app',
      template: `
        <div class="container-fluid content">
          <router-outlet></router-outlet>
        </div>
        <spinner [visible]="displayLoader"></spinner>
      `
    })
    
    export class AppComponent implements OnInit, OnDestroy {
        displayLoader: boolean;
        loaderSubscription: Subscription;
        constructor(private loaderService: LoaderService) {}
    
        ngOnInit() {
            this.loaderSubscription = this.loaderService.loaderCounter.subscribe((counter: number) => {
                this.displayLoader = counter != 0;
            });
        }
    
        ngOnDestroy() {
            this.loaderSubscription.unsubscribe();
        }
    }
    
  4. Using the loader service:

     import { LoaderService } from './core/loader.service';
        ..
        export class SampleComponent implements OnInit {
            constructor(private _service: SomeService, private loader: LoaderService) { }
    
        ngOnInit() {
            this.loader.displayLoader(true);
            this._service.getBalance().subscribe(
                response => ..do something..,
                () => .. error..,
                () => this.loader.displayLoader(false)
            );
        }
    }
    
Mark Cutajar
  • 340
  • 1
  • 6
  • Very cool...but why is your ajax call inside of the component? I think you should call the loader service method inside of another service where the ajax call is made, no? – MadCatm2 Oct 26 '17 at 14:51
  • From my knowledge of Angular 2, you have a service, and you simply call and bind at component level to the values you have. I'm not quite sure why you would require multiple services as you still need to bind the response with the model / variables inside the component. – Mark Cutajar Oct 27 '17 at 08:31
  • @MarkCutajar but the thing is here we purpose fully loading loader in component where it lacks originality of the question. – k11k2 Mar 16 '18 at 04:49
  • @k11k2 Agreed, the above code is not for every HttpRequest, to do so, creating HttpWrapper, and call the loader service from the HttpWrapper itself as MadCatm2 pointed out. With the posted code it gives more control on when to show it, depends on the use case needed. – Mark Cutajar Jun 20 '18 at 18:07
1

Just for the people who gets here from now on...

With this solution the spinner will not stop in case of error with the http request. Make sure you do the following:

...
return this.http.post(url, data, { headers: headers })
  .do(data=> {this.spinner.stop()},
  err=> {this.spinner.stop());
...
Gabriel
  • 393
  • 3
  • 12
0

You could also use Pace.js

Would be pretty easy to add

<head>
  <script src="/pace/pace.js"></script>
  <link href="/pace/themes/pace-theme-barber-shop.css" rel="stylesheet" />
</head>

You can find the documentation here: http://github.hubspot.com/pace/

Jamie Rees
  • 7,973
  • 2
  • 45
  • 83