Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to catch an error on a Request, then open a modal, then retry when modal closes with RxJS

I want to make a call to a server that can return an authorization fail (401) with Angular2's HTTP class.

The flow of the request should look like that:

  • The user makes a request to the server with myService.getSomething().subscribe()
  • If the server returns a 401: open a modal window asking the user for his credentials.
  • The user successfully log back into the application
  • The modal closes and executes a callback
  • The callback should retry the initial request (myService.getSomething().subscribe())

Here is what I have for the moment:

export class MyService {
    // ...
    public getSomething(): Observable<Response> {
        return this.http.get(url, options).catch((res: any, ob: any) => this.errorHandler(res, ob));
    }

    public errorHandler(res: Response, ob: Observable<any>): Observable<Response> {
        if (res.status === 401) {
            this.modalService.open(new ModalConfig({
                content: LoginModalComponent,
                close: () => { ob.retry(1); console.log("weow") } // <=close is the callback that should initiate the retry action on the initial request.
            }));
        }
        else {
            return Observable.throw(res.json());
        }
    }
}

doSomething() is used like that: doSomething().map((r) => r.json()).subscribe((r) => ....)

Update 1

I modified my code to look like @Thierry Templier's solution.

private errorHandler(res: Response, ob: Observable<any>): Observable<Response> {
    if (res.status === 401) {
        let closedSubject = new Subject();
        this.modalService.open(new ModalConfig({
            content: LoginModalComponent,
            close: () => { closedSubject.next(res);} // I also tried .complete(), .next(null), .next(true), .next(false)
        }));
        return ob.retryWhen(() => closedSubject);
    }
    else {
        return Observable.throw(res.json());
    }
}

Sadly it still doesn't work. The retryWhen is executed right away and doesn't wait for closedSubject.next() to be called. Therefore it starts an infinite loop, spamming the original Observable (the getSomething() function).

Update 2

I created a plunker to demonstrate the infinite loop:

https://plnkr.co/edit/8SzmZlRHvi00OIdA7Bga

Warning: running the plunker will spam your console with the string 'test'

Update 3

Following Thierry's correct answer, I tried to find a way to not use the source field since it is protected. After asking on rxjs's issue tracker to make the field public, a contributor replied with a better solution.

public get(url: string, options?: RequestOptionsArgs): Observable<Response> {
    return super.get(url, options).retryWhen((errors: any) => this.errorHandler(errors));
}
private errorHandler(errors): any {
    return errors.switchMap((err) => {
        if (err.status === 401) {
            let closedSubject = new Subject();
            this.modalService.open(new ModalConfig({
                content: LoginModalComponent,
                close: () => { closedSubject.next(err); }
            }));
            return <any>closedSubject;
        }
        else {
            return Observable.throw(err.json());
        }
    });
}

I avoid using .catch so I don't have to use the source field.

like image 870
Jean-Philippe Leclerc Avatar asked Apr 15 '16 18:04

Jean-Philippe Leclerc


2 Answers

I think that you need to return something an observable even in the case of the 401 error:

public errorHandler(res: Response, ob: Observable<any>): Observable<Response> {
    if (res.status === 401) {
        let closedSubject = new Subject();
        this.modalService.open(new ModalConfig({
            content: LoginModalComponent,
            close: () => {
              closedSubject.next();
        }));
        return ob.retryWhen(() => closedSubject);
    }
    else {
        return Observable.throw(res.json());
    }
}

See this article for more details: https://jaxenter.com/reactive-programming-http-and-angular-2-124560.html.

Edit

The problem is that the second parameter of the catch callback isn't the source observable. This source observable corresponds to the value of its source property:

return ob.source.retryWhen((errors) => closedSubject);

See the working plunkr: https://plnkr.co/edit/eb2UdF9PSMhf4Dau2hqe?p=preview.

like image 117
Thierry Templier Avatar answered Oct 08 '22 02:10

Thierry Templier


I guess retryWhen operator should help.

like image 21
kemsky Avatar answered Oct 08 '22 02:10

kemsky