Search code examples
angulartypescriptrxjstypescript1.8

Loading indicator on Http requests


The root of my problem is displaying a loading indicator on http requests, and I want to do it at the service level without having to write code for each component. What I did was implement an http wrapper that basically does this:

getMyHttpObservable(...) {
    setLoadingIndicator(true);
    const myHttpObservable = 
        createRequestObservable().finally(()=> {
            console.log('http done');
            setLoadingIndicator(false);
        });
    return myHttpObservable;
}

I have a sequence of observables which I chain using Observable.concat as follows:

const t1 = Observable.timer(5000).finally(()=>console.log('t1 done'));
const t2 = getMyHttpObservable(...);
const agg = Observable.concat(t1,t2);

I am using an event based cancellation technique using Observable.takeUntil as follows:

const ev = new EventEmitter<any>();
const cancellableAgg = agg.takeUntil(ev.asObservable());

Now the problem is when I cancel the aggregate observable during the first timer period I do not get the finally call of the 2nd observable - the http request. Example:

const cancel = Observable.timer(500).finally(()=>ev.emit());
cancellableAgg.subscribe();
cancel.subscribe();

When the above runs, I get this output:

t1 done

And I need to get http done as well, so that the loading indicator is cleared. How can I achieve that?

Thanks!

EDIT

I failed to realize that the concat operator only subscribes to observables down the line, when their predecessors have completed. What I need is to activate my loading indicator only when the http observable has actually been subscribed to.

I modified my wrapper function as follows:

getMyHttpObservable(...) {
    const myHttpObservable = createRequestObservable(...);
    const subscriptionAware = new Observable(subscriber => {
        setLoadingIndicator(true);
        myHttpObservable.subscribe(
            nextEv => subscriber.next(nextEv),
            errEv => subscriber.error(errEv),
            () => subscriber.complete())
        .add(()=>setLoadingIndicator(false));
    };
    return subscriptionAware;
}

So basically, I'm creating an Observable which transparently wraps another Observable, but also performs some additional actions when it's actually subscribed to - activate the loading indicator.

However, now I've lost the ability to cancel XHR requests in flight, since the constructor of Observable doesn't allow me to pass a handler for cancellation, so the inner subscription will never get cancelled.

Any ideas as to how to achieve that?


Solution

  • Managed to find a solution using the built in defer operator which waits for a subscription and returns a fresh observable:

    getMyHttpObservable(...) {
        const myHttpObservable = createRequestObservable(...);
        return Observable.defer(() => {
            setLoadingIndicator(true);
            return myHttpObservable.finally(() => setLoadingIndicator(false));
        });
    }
    

    Elegant and short.