This question is a bit similar Ionic 2 - Get token from Storage value and set Header before HTTP Request ( not a duplicate)
But the issue is connected with returning a value from local storage.
I need to set the default header(Authorization token) for all requests.
Must be a typical problem, but can't find a solution. And most of the available info - about setting it for each request. Which is simple, but not very reasonable. - why set for each request if possible to set for all.
i tried:
case #1:
import {Injectable} from "@angular/core";
import {BaseRequestOptions, RequestOptions, RequestOptionsArgs} from "@angular/http";
import { Storage } from '@ionic/storage';
@Injectable()
export class MyRequestOptions extends BaseRequestOptions {
constructor(private storage: Storage) {
super();
this.headers.set('Content-Type', 'application/json');
}
merge(options?: RequestOptionsArgs): RequestOptions {
const newOptions = super.merge(options);
console.log("setting auth header");
function setAuthHeader(storage) {
return storage.get('jwt_token').then(value => {
newOptions.headers.set('Authorization',`Bearer ${value}`);
return newOptions;
});
}
return setAuthHeader(this.storage).then(()=>{
return newOptions;
})
}
}
in this case it's just not compiling, but it's to show what do i want to do.
case #2:
@Injectable()
export class MyRequestOptions extends BaseRequestOptions {
constructor(private storage: Storage) {
super();
this.headers.set('Content-Type', 'application/json');
}
merge(options?: RequestOptionsArgs): RequestOptions {
const newOptions = super.merge(options);
console.log("setting auth header");
return this.getApiToken().flatMap( data => {
newOptions.headers.set('Authorization',`Bearer ${data}`);
return newOptions;
});
}
getApiToken(): Observable<RequestOptions> {
return Observable.fromPromise(this.storage.get('jwt_token'));
}
}
(it is similar to mentioned SO topic), but .flatMap()
throwing a error:
argument of type '(data: Headers) => RequestOptions' is not assignable to parameter of type '(value: Headers, index: number) => ObservableInput<{}>'. Type 'RequestOptions' is not assignable to type 'ObservableInput<{}>'. Type 'RequestOptions' is not assignable to type 'ArrayLike<{}>'. Property 'length' is missing in type 'RequestOptions'.
case #3:
let api_token = await this.storage.get('jwt_token');
-> await
simply isn't work here.
Any idea?
I've actually implemented this inside my Ionic Application, please see below:
I created a custom Http Interceptor,
http.interceptor.ts
import { Events } from 'ionic-angular';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import 'rxjs';
import { Storage } from '@ionic/storage';
import {Http, RequestOptionsArgs, Response, RequestOptions, ConnectionBackend, Headers} from '@angular/http';
import {Observable} from 'rxjs/Observable';
import {Storage} from '@ionic/storage';
import 'rxjs/add/operator/toPromise';
import 'rxjs/add/operator/map';
export class HttpInterceptor extends Http {
constructor(connectionBackend: ConnectionBackend, requestOptions: RequestOptions, public storage: Storage) {
super(connectionBackend, requestOptions);
}
public get(url: string, options?: RequestOptionsArgs): Observable<Response> {
return Observable.fromPromise(
this.getRequestOptionArgs(options)
).mergeMap((options) => {
return super.get(url, options)
});
}
public post(url: string, body: string, options?: RequestOptionsArgs): Observable<Response> {
return Observable.fromPromise(
this.getRequestOptionArgs(options)
).mergeMap((options) => {
return super.post(url, body, options)
})
}
public put(url: string, body: string, options?: RequestOptionsArgs): Observable<Response> {
return Observable.fromPromise(
this.getRequestOptionArgs(options)
).mergeMap((options) => {
return super.put(url, body, options)
})
}
public delete(url: string, options?: RequestOptionsArgs): Observable<Response> {
return Observable.fromPromise(
this.getRequestOptionArgs(options)
).mergeMap((options) => {
return super.delete(url, options)
});
}
private getRequestOptionArgs(options?: RequestOptionsArgs) {
return this.storage.get('token').then((token) => {
if (options == null) {
options = new RequestOptions();
}
if (options.headers == null) {
options.headers = new Headers();
}
if (token !== null) {
options.headers.append('Authorization', 'Bearer ' + token);
}
options.headers.append('Content-Type', 'application/json');
return options;
});
}
}
Inside my app.module.ts
app.module.ts
import {NgModule} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
import {IonicApp, IonicModule} from 'ionic-angular';
import {Storage, IonicStorageModule} from '@ionic/storage';
import {HttpModule, XHRBackend, RequestOptions, Http} from '@angular/http';
import {HttpInterceptor} from '../auth/http.interceptor';
import {SplashScreen} from "@ionic-native/splash-screen";
import {StatusBar} from '@ionic-native/status-bar';
import {Keyboard} from '@ionic-native/keyboard';
import {InAppBrowser} from '@ionic-native/in-app-browser';
export function httpInterceptorFactory(xhrBackend: XHRBackend, requestOptions: RequestOptions, storage: Storage) {
return new HttpInterceptor(xhrBackend, requestOptions, storage);
}
@NgModule({
declarations: [
...
],
imports: [
BrowserModule,
IonicModule.forRoot(MyApp, {mode: 'md'}),
IonicStorageModule.forRoot(),
HttpModule
],
bootstrap: [IonicApp],
entryComponents: [
...
],
providers: [StatusBar, SplashScreen, Keyboard, InAppBrowser, //these are just things I needed in my app
{
provide: Http,
useFactory: httpInterceptorFactory,
deps: [XHRBackend, RequestOptions, Storage]
}
]
})
export class AppModule {
}
Then inside my provider I used it like a regular http request
app.provider.ts
import { Injectable } from '@angular/core';
import { Http, Response } from '@angular/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/Rx';
import { CONFIG } from '../config/app.config';
@Injectable()
export class AppProvider {
private baseUrl:string = CONFIG.apiEndpoint;
constructor(private http: Http) {
}
public getSomething():Observable<any> {
let url:string = this.baseUrl + 'some endpoint';
return this.http.get(url).map((res:Response) => res.json());
}
}
Hopefully this helps
It seems the best way to achiev this is by implementing Interceptors. However, the current (at the time of writing this answer) version of Ionic is using Angular 4.1.3, which uses HttpModule, which does not yet implement interceptors natively. The next version of Angular 4.3.1 (https://angular.io/guide/http) uses HttpClientModule, which does have Interecptors.. The way to do it with interceptors is to capture the request, read from storage, which should return a promise (https://ionicframework.com/docs/storage/ - look at the bottom "Instance Members"). Then on resolve, you call next.handle(req), which will go to the next/final event which is the API request.
The solution I am implementing for now (which might be a waste of time, if Ionic updates to Angular 4.3.1 in the next week .... hint hint), is to write a "custom" interceptor, by extending Http (https://medium.com/aviabird/http-interceptor-angular2-way-e57dc2842462).
Good luck!