Angular rxjs: keep subscription after error reached

Viewed 1702

I need to know how to keep subscription after an error has thrown.

I've created this observable:

let saveClick$ = Observable.fromEvent(this.saveButton.nativeElement, 'click');

So, I've tried with Observable.empty, but as far I've been able to figure out it emits an complete, so subscriber will be removed:

this.saved$ = saveClick$.pipe(this.pushUser(), catchError(() => Observable.empty<AdministrationUser>()), share());

I want that when any error is reached, subscription is kept.

Any ideas?

Additional code:

// Custom pipes
private push = <T>() => switchMap<T, AdministrationUser>(() => this.service.push(this.user));
private handleError = <T>() => catchError<T, Array<{code: string, message: string}>>((error: ResponseError) => Observable.of(error.errors));
private handleEmptyUser = <T>() => catchError<T, AdministrationUser>(() => Observable.of(UsuarisadministracioSubcomponentComponent.EMPTY_USER));

private pushUser = () => pipe( //simplified pipe
    this.push()
);

And my service is:

const buildURL = () => map((filter: {userId: string} & Trace) => this.buildPushURL(filter.currentUser, filter.currentApplication, filter.userId));
const makeRequest = () => switchMap((url: string) => httpMethodFn.call(this.authHttp, url, user));
const buildResponse = () => map(() => user);
const onErrorGetDetails = () => catchError((error: Response) => Observable.throw(<ResponseError>error.json()));

return Observable.of({userId: user.id})
    .pipe(
        buildURL(),
        makeRequest(),
        buildResponse(),
        onErrorGetDetails()
    );
2 Answers

You should catch the error in the inner observable pipeline instead of source observable. If you catch the error on source observable then as per the rxjs concept the source observable will not emit new values after an error occurs. So catch the error in inner observable like this:

this.saved$ = saveClick$.pipe(
                               //i am assuming this.pushUser() is a higher order function which returns an observable
                               this.pushUser()
                                   .pipe(
                                          catchError(() => Observable.empty<AdministrationUser>())
                                        ),
                                        share()
                                    );

Catching the error in inner observable will keep your source observable alive and it will keep emitting the values even if your inner observable throws error. You can move your share() operator as per your need [i.e. either inside your inner observable or in source observable pipe line] but the idea remains same - catch the error inside the inner observable pipeline to keep your source (outer) observable alive.

EDIT 1: [Suggesting a simplified code as per the latest code of the user]

Please define your push method in your service like this:

push(user) {

    //By seeing your code; I could not figure out where and how are you passing the parameter in buildUrl
    //It appears to me that this.buildPushURL method simply return an URL based on the passed parameters and IT DOES NOT MAKE
    //ANY HTTP CALL TO YOUR BACKEND. IF THAT IS TRUE - 
    //THEN please adjust the below code accordingly as I dont know how you use this method OR PLEASE provide some more detaling 
    //on  this.buildPushURL() method
    const url = this.buildPushURL(filter.currentUser, filter.currentApplication, filter.userId);

    //I am assuming that httpMethodFn calls http.get() or http.post() and returns an observable
    //Suggestion - Why you dont use httpClient.get or httpClient.post() directly? It will avoid to call
    //httpMethodFn by httpMethodFn.call?

    //Bottom line is - httpMethodFn.call must return an observable otherwise adjust your httpMethodFn.call code to return an observable
    //to make below code work.
    return httpMethodFn.call(this.authHttp, url, user)
                       .pipe(
                           map(user => {
                               console.log(user);
                               return user;
                           }),
                           catchError(error => throwError(<ResponseError>error.json()))
                       );

}

And use it like this -

yourMethodReturnsAnObservableWhichIsSubscribeByTheConsumer() {

    const saveClick$ = Observable.fromEvent(this.saveButton.nativeElement, 'click');

    return saveClick$.pipe(

            switchMap(() => {
                return this.push(user)
                            .pipe(
                                //you catch error here so that your outer observable will keep alive

                                //please adjust you code as per your need
                                //I guess it will give you an idea
                                handleError()
                            )
            })


    );
}

Hope you will get an idea of how to simplify your code. I could not provide a perfect solution as I am not aware of your design and complete code. I am pretty sure that the above code will give you an idea.

A suggestion - It is good to use the higher-order function but understand that do not overuse them as it makes your code a bit difficult to understand [PS - It is my personal opinion and It is good to have different opinions..:)]. Keep your observable chain as simple as possible by applying the functional style of coding. RXJS is awesome:)

You need to use the catchError operator and return the NEVER Observable

 import { NEVER } from 'rxjs';
 import { catchError } from 'rxjs/operators';

 observable$.pipe(catchError((error: any) => {return NEVER;}));
Related