My goal is to implement a call to a server using Angular2's HTTP class that can handle authorization failures (401).
The process should follow these steps:
- The user initiates a request to the server with myService.getSomething().subscribe()
- If the server responds with a 401 error, prompt a modal window asking for user credentials.
- User successfully logs back into the application
- The modal closes and triggers a callback
- The callback retries the initial request (myService.getSomething().subscribe())
This is what I have currently:
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") }
}));
}
else {
return Observable.throw(res.json());
}
}
}
doSomething() usage:
doSomething().map((r) => r.json()).subscribe((r) => ....)
Update 1
I revised my code based on @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);}
}));
return ob.retryWhen(() => closedSubject);
}
else {
return Observable.throw(res.json());
}
}
Unfortunately, it still doesn't function as intended. The retryWhen executes immediately without waiting for closedSubject.next() to be called, resulting in an infinite loop.
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
After following Thierry's correct answer, I searched for a way to avoid using the protected source field. Upon requesting to make the field public on rxjs's issue tracker, a contributor provided 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 opted not to use .catch to avoid utilizing the source field.