Hey guys, doing a quick post here to spare another human the pain I just went through to get this working.
The problem:
Access the ionic storage. Get your token, modify the request headers and send the request.
Sounds easy but not quite. The problem is that the promise is asynchronous which means it does not care whether you have the token or not. It will send your request with most likely a null token as it does not wait. The async/wait approach did not work.
Below is my code which i modified for the ionic storage off a solution I saw somewhere for a similar Angular 7 problem. The important things to pay attention to here is the from and SwitchMap imports from rxjs.
Hope this helps!
Code:
import {Injectable} from '@angular/core';
import {
HttpInterceptor,
HttpRequest,
HttpResponse,
HttpHandler,
HttpEvent,
HttpErrorResponse
} from '@angular/common/http';
import { Observable, throwError, from } from 'rxjs';
import { map, catchError, switchMap } from 'rxjs/operators';
import { AlertController } from '@ionic/angular';
import { Storage } from '@ionic/storage';
const TOKEN_KEY = 'auth-token';
@Injectable()
export class HttpConfigInterceptor implements HttpInterceptor {
protected url = 'http://example.com/api';
protected debug = true;
constructor(private alertController: AlertController, private storage: Storage) {}
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
// YOU CAN ALSO DO THIS
// const token = this.authenticationService.getToke()
return from(this.storage.get(TOKEN_KEY))
.pipe(
switchMap(token => {
if (token) {
request = request.clone({ headers: request.headers.set('Authorization', 'Bearer ' + token) });
}
if (!request.headers.has('Content-Type')) {
request = request.clone({ headers: request.headers.set('Content-Type', 'application/json') });
}
if (this.debug) {
request = request.clone({ url: this.url + request.url + '?XDEBUG_SESSION_START=1'});
}
return next.handle(request).pipe(
map((event: HttpEvent<any>) => {
if (event instanceof HttpResponse) {
// do nothing for now
}
return event;
}),
catchError((error: HttpErrorResponse) => {
const status = error.status;
const reason = error && error.error.reason ? error.error.reason : '';
this.presentAlert(status, reason);
return throwError(error);
})
);
})
);
}
async presentAlert(status, reason) {
const alert = await this.alertController.create({
header: status + ' Error',
subHeader: 'Subtitle',
message: reason,
buttons: ['OK']
});
await alert.present();
}
}
I don’t understand why you are manually instantiating a Promise here. It’s generally an antipattern and looks to me like this situation is no exception to that characterization.
hello i’ll study this you wrote, im still a student in ionic-angular but in this case the token return null when i ask from the ui to the register service wich ask some register in the backend i thought because was not async and await the response of the save token function, i instance new promise and i colud use async and await inside and works.
From my understanding, this.storage.get() returns a Promise that gets converted to an Observable through RxJS.from().
This observable is then piped through switchMap. Why is that? Are you assuming the TOKEN_KEY can change at any time, and if it does in the middle of retrieving, then it will emit a new value to the observable and switchMap will cancel/forget about the previous value?
Here’s how I think of it. If I have an existing Observable that is giving me a Foo, I need a Bar instead, and I have:
a function that takes a Foo and gives me a Bar, then I want map;
a function that takes a Foo and gives me an Observable<Bar>, then I need either switchMap or mergeMap. In many cases, it doesn’t matter which - the only difference is whether I need the subscription to the initial Observable<Foo> to persist or not, If I’m OK to drop it once you have your Foo, I use switchMap. Otherwise, mergeMap.
@rapropos Thank you for providing some clarification. I’m still feeling a bit fuzzy on
the only difference is whether I need the subscription to the initial Observable<Foo> to persist or not, If I’m OK to drop it once you have your Foo, I use switchMap
Are you saying that this line of code will give me my Observable<Foo>
return from(this.storage.get(TOKEN_KEY))
Once I have it:
.pipe(
switchMap(token => { // the token here is my Observable<foo>
Inside the place where you made the comment "the token here is my Observable<foo>", the token there is the actual Foo, not the Observable<Foo>.
Think of it like a track-and-field relay. The first runner is from(this.storage.get(TOKEN_KEY)), who passes the baton (the token) to the next runner (who eventually fires off the HttpRequest).
switchMap (or mergeMap) is the operation of passing the baton, and the only difference between the two is how TV covers the first runner. If the baton is passed via switchMap, TV totally forgets about that first runner as soon as the baton is passed and they can sit right down. If the baton is passed via mergeMap, a TV camera (subscription) remains focused on the first runner as they keep running around the track with no baton or admiring their time on the scoreboard or blow kisses to the crowd or whatever.
Importantly for this particular situation, using from to generate an Observable from a Promise always makes a one-shot Observable that completes immediately after emitting its first and only value. Therefore the runner in our relay example is going to sit down regardless of whether there is a camera on them or not, so it absolutely doesn’t matter which you use. It only matters when the first Observable is longer-lived.
Here. In that case, instead of passing from a Promise, we’re passing it an array, which makes for a longer-lived Observable that keeps emitting until it’s finished all of the items in the array. Note the first operator is a switchMap because we only have one array, but after we’ve spread the array out we need to mergeMap the process that fetches each subrequest, otherwise we’d only fetch the first element.