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:
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.
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.
I guess retryWhen
operator should help.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With