import { Injectable } from '@angular/core';
import { Platform } from 'ionic-angular';
import { Observable } from 'rxjs/Observable';
import { HTTP, HTTPResponse } from '@ionic-native/http';
import 'rxjs/add/observable/throw';
import 'rxjs/add/observable/defer';
import 'rxjs/add/observable/fromPromise';
@Injectable()
export class HttpService {
private cachedObs = new Map<string, Observable<any>>();
constructor(private platform: Platform, private cordovaHttp: HTTP) {
}
request<T>(method: 'GET' | 'POST' | 'DELETE', url: string, options: {
body?: any;
params: Map<any, any>;
headers?: Headers;
responseType: 'json' | 'text';
}): Observable<HttpServiceResponse<T | any>> {
const urlWithParams = this.getUrlWithParams(url, options.params);
let obs: Observable<any>;
let obsKey = this.getObservableCacheKey(method, url, options);
if (this.cachedObs.has(obsKey)) {
return this.cachedObs.get(obsKey);
}
if (this.platform.is('cordova')) {
obs = this.cordovaRequest<T>(method, urlWithParams, options);
} else {
obs = this.browserRequest<T>(method, urlWithParams, options);
}
obs = obs
.map(data => {
this.cachedObs.delete(obsKey);
return data
})
.share();
this.cachedObs.set(obsKey, obs);
return obs;
}
get<T>(url: string, options?: {
headers?: Headers;
params?: Map<any, any>;
responseType?: 'json' | 'text';
getRawResponse?: boolean;
}): Observable<HttpServiceResponse<T | any> | T | any> {
let obs = this.request<T>('GET', url, {
headers: options ? options.headers : null,
params: options ? options.params : null,
responseType: options ? options.responseType : 'json',
});
if (options && options.getRawResponse) {
return obs;
} else {
return obs.map(res => this.handleReponse(res));
}
}
post<T>(url: string, body: any, options?: {
headers?: Headers;
params?: Map<any, any>;
responseType?: 'json' | 'text';
getRawResponse?: boolean;
}): Observable<HttpServiceResponse<T | any> | T | any> {
let obs = this.request<T>('POST', url, {
body: body,
headers: options ? options.headers : null,
params: options ? options.params : null,
responseType: options ? options.responseType : 'json',
});
if (options && options.getRawResponse) {
return obs;
} else {
return obs.map(res => this.handleReponse(res));
}
}
delete<T>(url: string, body: any, options?: {
headers?: Headers;
params?: Map<any, any>;
responseType?: 'json' | 'text';
getRawResponse?: boolean;
}): Observable<HttpServiceResponse<T | any> | T | any> {
let obs = this.request<T>('DELETE', url, {
body: body,
headers: options ? options.headers : null,
params: options ? options.params : null,
responseType: options ? options.responseType : 'json',
});
if (options && options.getRawResponse) {
return obs;
} else {
return obs.map(res => this.handleReponse(res));
}
}
private handleReponse<T>(response: HttpServiceResponse<T>): Observable<any> | T {
if (!response.ok) {
throw Observable.throw(response);
}
return response.data
}
private getUrlWithParams(url, params?: Map<any, any>) {
let queryStrings = [];
if (params) {
params.forEach((value, key) => {
if (Array.isArray(value)) {
value.forEach(v => {
queryStrings.push(standardEncoding(key) + '=' + standardEncoding(v));
})
} else {
queryStrings.push(standardEncoding(key) + '=' + standardEncoding(value));
}
})
}
if (queryStrings.length === 0) {
return url;
}
// Does the URL already have query parameters? Look for '?'.
const qIdx = url.indexOf('?');
// There are 3 cases to handle:
// 1) No existing parameters -> append '?' followed by params.
// 2) '?' exists and is followed by existing query string ->
// append '&' followed by params.
// 3) '?' exists at the end of the url -> append params directly.
// This basically amounts to determining the character, if any, with
// which to join the URL and parameters.
const sep: string = qIdx === -1 ? '?' : (qIdx < url.length - 1 ? '&' : '');
return url + sep + queryStrings.join('&');
}
private browserRequest<T>(method: 'GET' | 'POST' | 'DELETE', url: string, options: {
body?: any;
headers?: Headers;
responseType: 'json' | 'text';
}): Observable<HttpServiceResponse<T>> {
const responseType = options.responseType || 'json';
return Observable.defer(() => {
return Observable.fromPromise(
fetch(url, {
method: method,
body: options.body ? JSON.stringify(options.body) : null,
headers: options.headers || new Headers(),
})
.then(
(response: Response) => {
const httpResponse: HttpServiceResponse<T> = {
ok: response.ok,
status: response.status,
data: null
};
if (!response.ok) {
httpResponse.errorMessage = 'Error ' + response.status + ' on calling ' + method + ' ' + url;
return httpResponse;
}
const contentType = response.headers.get("content-type");
if (contentType && contentType.includes("application/json")) {
return response.json().then(json => {
httpResponse.data = json;
return httpResponse;
});
} else {
return response.text().then(text => {
httpResponse.data = responseType === 'json' ? JSON.parse(text) : text;
return httpResponse;
});
}
},
(err: TypeError) => {
return <HttpServiceResponse<T>>{
ok: false,
status: null,
data: null,
errorMessage: err.message
};
}
)
);
});
}
private cordovaRequest<T>(method: 'GET' | 'POST' | 'DELETE', url: string, options: {
body?: any;
headers?: Headers;
responseType: 'json' | 'text';
}): Observable<HttpServiceResponse<T>> {
const responseType = options.responseType || 'json';
let headers = {};
if (options.headers) {
options.headers.forEach((value, key) => {
headers[key] = value;
})
}
this.cordovaHttp.setDataSerializer('json');
let promise;
if (method === 'GET') {
promise = this.cordovaHttp.get(url, {}, headers);
} else if (method === 'POST') {
promise = this.cordovaHttp.post(url, options.body, headers);
} else if (method === 'DELETE') {
promise = this.cordovaHttp.delete(url, options.body, headers);
}
return Observable.defer(() => {
return Observable.fromPromise(
promise
.then(
(response: HTTPResponse) => {
const httpResponse: HttpServiceResponse<T> = {
ok: response.status >= 200 && response.status <= 299,
status: response.status,
data: null
};
if (httpResponse.ok) {
httpResponse.data = responseType === 'json' ? JSON.parse(response.data) : response.data;
} else {
httpResponse.errorMessage = 'Error ' + response.status + ' on calling ' + method + ' ' + url;
}
return httpResponse;
},
response => {
const httpResponse: HttpServiceResponse<T> = {
ok: response.status >= 200 && response.status <= 299, // Weird isn't it?
status: response.status,
data: null
};
if (httpResponse.ok) {
httpResponse.data = responseType === 'json' ? JSON.parse(response.error) : response.error;
} else {
httpResponse.errorMessage = response.error;
}
return httpResponse;
}
)
)
});
}
private getObservableCacheKey(method, url: string, options: {
body?: any;
params: Map<any, any>;
headers?: Headers;
responseType: 'json' | 'text';
}) {
let headers = {};
if (options.headers) {
options.headers.forEach((value, key) => {
headers[key] = value;
})
}
let params = {};
if (options.params) {
options.params.forEach((value, key) => {
params[key] = value;
})
}
return JSON.stringify({
method: method,
url: url,
body: options.body,
params: params,
headers: headers,
responseType: options.responseType,
});
}
}
export interface HttpServiceResponse<T> {
ok: boolean;
status: number;
data: T;
errorMessage?: string;
}
//Get from angular httpClient
function standardEncoding(v: string): string {
return encodeURIComponent(v)
.replace(/%40/gi, '@')
.replace(/%3A/gi, ':')
.replace(/%24/gi, '$')
.replace(/%2C/gi, ',')
.replace(/%3B/gi, ';')
.replace(/%2B/gi, '+')
.replace(/%3D/gi, '=')
.replace(/%3F/gi, '?')
.replace(/%2F/gi, '/');
}
Use this service instead of the angular one (Http or HttpClient). It will also make your app smaller.