5

I would like to turn on my loader icon (e.g. spinner) isLoaderEnabled when data starts to download from API and turn it off when successfully received. What would be a proper way to turn it off?

Here is my following code.

My component:

this.isLoaderEnabled = true;
this.meetingService.getList();

My service:

getList() {
  this._http.get('../fake-data/someFile.json')
    .map(response => response.json())
    .subscribe(
      data => {
        this.dataStore.meetingList = data;
        this.meetingListObserver.next(this.dataStore.meetingList);
      },
      err => console.log('>>>', 'Could not load meetings list. Error: ', err),
      () => console.log('>>>', 'Done with getting meetings list.')
    );
}

Thank you.

be-codified
  • 5,704
  • 18
  • 41
  • 65

4 Answers4

8

I think that your issue is trickiest than it appears at a first sight ;-) In fact, XHR doesn't support streaming so when the first callback registered within the subscribe method is called you already received all the data.

That said XHR supports the onprogress event (see this plunkr: http://plnkr.co/edit/8MDO2GsCGiOJd2y2XbQk?p=preview). This allows to get hints about the progress of the download. This isn't supported out of the box by Angular2 but you

@Injectable()
export class CustomBrowserXhr extends BrowserXhr {
  constructor(private service:ProgressService) {}
  build(): any {
    let xhr = super.build();
    xhr.onprogress = (event) => {
      service.progressEventObservable.next(event);
    };
    return <any>(xhr);
  }
}

and override the BrowserXhr provider with the extended:

bootstrap(AppComponent, [
  HTTP_PROVIDERS,
  provide(BrowserXhr, { useClass: CustomBrowserXhr })
]);

The progress service could be implemented this way:

export class ProgressService {
  progressEventObservable:Subject<any> = new Subject();
}

Your service could subscribe on the progress service to be notified when the download actually starts (i.e. the first time the next method of progressEventObservable is called for the request):

getList() {
  var subscription = this.progressService.subscribe(event => {
    if (!this.spinner) {
      this.spinner = true;
    }
  });
  this._http.get('../fake-data/someFile.json')
    .map(response => response.json())
    .do(val => this.spinner = true)
    .subscribe(
      data => {
        this.dataStore.meetingList = data;
        this.meetingListObserver.next(this.dataStore.meetingList);
      },
      err => (...),
      () => {
        this.spinner = false;
        subscription.unsubscribe();
      })
    );
}

Edit

If you want to use the spinner within the component and not the service. You could use a dedicated observable / subject, as described below:

spinner$: Subject<boolean> = new Subject();
spinner: boolean = false;

getList() {
  var subscription = this.progressService.subscribe(event => {
    if (!this.spinner) {
      this.spinner = true;
      this.spinner$.next(this.spinner); // <-------
    }
  });
  this._http.get('../fake-data/someFile.json')
    .map(response => response.json())
    .do(val => this.spinner = true)
    .subscribe(
      data => {
        this.dataStore.meetingList = data;
        this.meetingListObserver.next(this.dataStore.meetingList);
      },
      err => (...),
      () => {
        this.spinner = false;
        this.spinner$.next(this.spinner); // <-------
        subscription.unsubscribe();
      })
    );
}

The component can subscribe on spinner$ to be notified of changes:

@Component({
  (...)
})
export class SomeCOmponent {
  constructor(private meetingService:MeetingService) {
    this.meetingService.spinner$.subscribe((spinner) {
      this.isLoaderEnabled = spinner;
    });
  }
}
Thierry Templier
  • 198,364
  • 44
  • 396
  • 360
  • One thing more, I am having my spinner in my component (not the service). This changes things? – be-codified Apr 11 '16 at 20:54
  • You're welcome! You need to subscribe in the component if your spinner is in it... – Thierry Templier Apr 11 '16 at 21:00
  • Could you elaborate this? I am having two files, one is component (this is where spinner gets state true or false; service is called), the other one is service - please see the initial question. – be-codified Apr 11 '16 at 21:29
  • Yes, sure! I updated my answer to be close to your classes. – Thierry Templier Apr 12 '16 at 07:42
  • Thierry much appreciated, thank you again! I will dive into it. – be-codified Apr 12 '16 at 11:49
  • I was thinking in general why we needed to use `progressService`, would it be enough to just turn spinner off after `meetingListObserver` passes new data to observable? (`meetingListObserver.next(this.dataStore.meetingList)`). The "download" is finished then. – be-codified Apr 12 '16 at 12:55
  • Yes, you're right. The problem is rather for the start of receiving data ;-) For the end, you're absolutely right! – Thierry Templier Apr 12 '16 at 12:57
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/108949/discussion-between-be-codified-and-thierry-templier). – be-codified Apr 12 '16 at 14:25
2
getList() {
  this._http.get('../fake-data/someFile.json')
    .map(response => response.json())
    .do(val => this.spinner = true)
    .subscribe(
      data => {
        this.dataStore.meetingList = data;
        this.meetingListObserver.next(this.dataStore.meetingList);
      },
      err => console.log('>>>', 'Could not load meetings list. Error: ', err),
      () => this.spinner = false)
    );
}
Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
1

You need to make few tricks to make it work properly:

    //use Observable.from as lock trigger BEFORE request is sent
    var request:Observable<Response> = Observable.from([1], null).do(()=>
    {
        lock.lock();
    }).flatMap(()=>
    {
        //use finally to unlock in any case
        return this.http.post(url, body, options).finally(()=>
        {
            lock.unlock();
        });
    });
    //share sequence to avoid multiple calls when using several subscribers
    return request.share();
kemsky
  • 14,727
  • 3
  • 32
  • 51
0

If your component subscribe()s to the meetingListObserver, just turn it off in the subscribe callback function.

If not, add another Observable or a Subject to the service and have the component subscribe to it. When the data comes back from the service, call next() on the Observable or Subject. For an example of using a Subject in a service, see https://angular.io/docs/ts/latest/cookbook/component-communication.html#!#bidirectional-service

Mark Rajcok
  • 362,217
  • 114
  • 495
  • 492