Reputation: 2839
I have a subscription which has asynchronous step - api call. While api call is in progress I want to show loading animation. It can be easily done when stream created with access to the UI:
Rx.Observable
.combineLatest(page$, sorting$)
.switchMap(function(args) {
var page = args[0];
var sorting = args[1];
$('.overlay').show();
return loadData(page, sorting);
})
.subscribe(function(data) {
renderData(data);
$('.overlay').hide();
});
But when I move stream management code to the service (e.g. for code reuse) the ability to track asynchronous operation is lost and I can't show loading animation.
Any ideas how it can be done? Return two stream from the service?
Thanks in advance.
Upvotes: 2
Views: 421
Reputation: 2839
Eventually I ended up using custom operator:
class RequestState {
loading: boolean;
}
Observable.prototype.captureState = function captureState<T>(this: Observable<T>, state: RequestState): Observable<T> {
state.loading = true;
let onLoadingEnd = () => {
state.loading = false;
};
obs = obs.do(onLoadingEnd, onLoadingEnd, onLoadingEnd);
return obs;
};
class Component {
requestState = new RequestState();
ngOnInit() {
this.api.endpoint(query)
.captureState(this.requestState)
.subscribe(...);
}
}
Upvotes: 0
Reputation: 9425
Why not create a stream which contains multiple 'events' - started
and finished
, something like this:
function loadDataWithStartEvent($page, sorting$) {
return Rx.Observable
.combineLatest(page$, sorting$)
.flatMap(args => {
var page = args[0];
var sorting = args[1];
return Rx.Observable.just({ event : 'started' })
.concat(
// given that loadData returns an Observable<data> which only emits data one time
loadData(pageSortingTuple.page, pageSortingTuple.sorting))
.map(data => ({ event: 'finished', data: data}))
);
});
}
Now you subscribe as follows:
loadDataWithStartEvent(page$, sorting$)
.doOnNext(evt => {
if(evt.type == 'started') {
$('overlay').show();
} else {
$('overlay').hide();
}
})
.filter(evt => evt.type === 'finished')
.map(evt => evt.data);
.subscribe(data => renderData(data));
Upvotes: 2
Reputation: 14385
You can pass an extra notification object to the setupLoading
:
setupLoading: function(page$, sorting$, notify$) {
return Rx.Observable
.combineLatest(page$, sorting$)
.switchMap(function(args) {
notify$.next(true); // here we go...
var page = args[0];
var sorting = args[1];
return loadData(page, sorting);
});
}
and then create, in addition to the page$
and sorting$
and loadStarted$
observable.
loadStarted$.subscribe(() => $('.overlay').hide();
and your loader creation will be:
service.setupLoading(page$, sorting$, loadStarted$)
.subscribe(function(data) {
renderData(data);
});
});
Btw, since you use rxjs so nicely, why not use it for the show/hide - the angular2 way - and give up jquery:
<div class="overlay" [style.display]="(showOverlay ? 'block' : 'none')>your overlay content here</div>
Upvotes: 1