Since i've started with angular2 i have setup my services to return Observable of T. In the service i would have the map() call, and components using these services would just use subscribe() to wait for the response. For these simple scenarios i didnt really need to dig in to rxjs so all was ok.
I now want to achieve the following: i am using Oauth2 authentication with refresh tokens. I want to build an api service that all other services will use, and that will transparently handle the refresh token when a 401 error is returned. So, in the case of a 401, i first fetch a new token from the OAuth2 endpoint, and then retry my request with the new token. Below is the code that works fine, with promises:
request(url: string, request: RequestOptionsArgs): Promise<Response> {
var me = this;
request.headers = request.headers || new Headers();
var isSecureCall: boolean = true; //url.toLowerCase().startsWith('https://');
if (isSecureCall === true) {
me.authService.setAuthorizationHeader(request.headers);
}
request.headers.append('Content-Type', 'application/json');
request.headers.append('Accept', 'application/json');
return this.http.request(url, request).toPromise()
.catch(initialError => {
if (initialError && initialError.status === 401 && isSecureCall === true) {
// token might be expired, try to refresh token.
return me.authService.refreshAuthentication().then((authenticationResult:AuthenticationResult) => {
if (authenticationResult.IsAuthenticated == true) {
// retry with new token
me.authService.setAuthorizationHeader(request.headers);
return this.http.request(url, request).toPromise();
}
return <any>Promise.reject(initialError);
});
}
else {
return <any>Promise.reject(initialError);
}
});
}
In the code above, authService.refreshAuthentication() will fetch the new token and store it in localStorage. authService.setAuthorizationHeader will set the 'Authorization' header to previously updated token. If you look at the catch method, you'll see that it returns a promise (for the refresh token) that in its turns will eventually return another promise (for the actual 2nd try of the request).
I have attempted to do this without resorting to promises:
request(url: string, request: RequestOptionsArgs): Observable<Response> {
var me = this;
request.headers = request.headers || new Headers();
var isSecureCall: boolean = true; //url.toLowerCase().startsWith('https://');
if (isSecureCall === true) {
me.authService.setAuthorizationHeader(request.headers);
}
request.headers.append('Content-Type', 'application/json');
request.headers.append('Accept', 'application/json');
return this.http.request(url, request)
.catch(initialError => {
if (initialError && initialError.status === 401 && isSecureCall === true) {
// token might be expired, try to refresh token
return me.authService.refreshAuthenticationObservable().map((authenticationResult:AuthenticationResult) => {
if (authenticationResult.IsAuthenticated == true) {
// retry with new token
me.authService.setAuthorizationHeader(request.headers);
return this.http.request(url, request);
}
return Observable.throw(initialError);
});
}
else {
return Observable.throw(initialError);
}
});
}
The code above does not do what i expect: in the case of a 200 response, it properly returns the response. However, if it catches the 401, it will successfully retrieve the new token, but the subscribe wil eventually retrieve an observable instead of the response. Im guessing this is the unexecuted Observable that should do the retry.
I realize that translating the promise way of working onto the rxjs library is probably not the best way to go, but i havent been able to grasp the "everything is a stream" thing. I have tried a few other solutions involving flatmap, retryWhen etc ... but didnt get far, so some help is appreciated.
See Question&Answers more detail:
os