3

I'm trying to wrap the http service in angular 2 using this code:

@Injectable()
export class HttpSuperService {
    private baseUrl: string;
    constructor(private http: Http) {
    }

    get(url: string): Observable<string> {
        return (
            this.baseUrl ? 
            Observable.of(this.baseUrl) :
            this.http.get('/config/').map((res: Response) => res)
        )
        .flatMap((res: any) => {
            this.baseUrl = res._body;
            return this.http.get(this.baseUrl + url)
                .map((res2: Response) => res2.json());
        });
    }
}

What I'm trying to do is making the first request to get the baseUrl for the application (the API is on another URL) but only making that request once (the first time).

it works on the first request but the second request (IE when another component is using the same service) is not working since there is something wrong with the "Observable.of". I get that i need to use Response in some way instead of the string....That lead me to start thinking about this approach. Is there a better way to do this?

This solution works but feels a little to verbose since I plan to add other methods (POST, PUT) to this service as well:

@Injectable()
export class HttpSuperService {
private baseUrl: string;
constructor(private http: Http) {
}

get(url: string): Observable<string> {
    if (this.baseUrl) {
        return this.http.get(this.baseUrl + url).map((res2: Response) => res2.json());
    }
    return this.http.get('/config/').map((res: Response) => res)
        .flatMap((res: any) => {
            this.baseUrl = res._body;
            return this.http.get(this.baseUrl + url).map((res2: Response) => res2.json());
        });
    }
}
martenolofsson
  • 501
  • 1
  • 4
  • 20

2 Answers2

0

You can factor out the dependency on the retrieval of the API url into dedicated method. That method can also handle some of the intermediate steps such as url concatenation to further reduce duplication.

For example:

import {Http} from '@angular/http';
import {Inject} from '@angular/core';
import {Observable} from 'rxjs/Observable';
import 'rxjs/add/operator/mergeMap';
import 'rxjs/add/observable/fromPromise';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/mapTo';
import 'rxjs/add/operator/do';

let baseUrl: string;

@Inject export class HttpSuperService {
  routeUrl(url: string) {
    return baseUrl
      ? Observable.of(baseUrl + url)
      : this.http.get('/config/')
        .do(response => baseUrl = response.text())
        .mapTo(baseUrl + url);
  }

  constructor(readonly http: Http) {}

  get(url: string): Observable<string> {
    return this.routeUrl(url)
      .flatMap(this.http.get)
      .map(res => res.json());
  }

  post(url: string, body: Something): Observable<Something> {
    return this.routeUrl(url)
      .flatMap(url => this.http.post(url, body))
      .map(response => <Something>response.json());
  }
}

I think that is reasonable, but we can do better, we can be more DRY (there is no such thing as too dry :)):

@Inject export class HttpSuperService {
  constructor(readonly http: Http) {}

  routeUrl(url: string) {
    return baseUrl
      ? Observable.of(baseUrl + url)
      : this.http.get('/config/')
        .do(response => baseUrl = response.text())
        .mapTo(baseUrl + url);
  }

  request<R>(url: string, makeRequest: (url: string, body?: {}) => Observable<Response>) {
    return this.routeUrl(url)
      .flatMap(url => makeRequest(url))
      .map(response => <R>response.json());
  }

  get(url: string): Observable<string> {
    return this.request<string>(url, fullUrl => this.http.get(fullUrl));
  }

  post(url: string, body: Something): Observable<Something> {
    return this.request<Something>(url, fullUrl => this.http.post(fullUrl, body));
  }
}

I'm not entirely satisfied with the URL handling, and I would likely refactor it further, but we have cut down on duplication significantly.

Aluan Haddad
  • 29,886
  • 8
  • 72
  • 84
  • 1
    Hi! Thanks for your comment, I removed my answer to avoid giving bad info to others. But, if you can or have time, could you explain me a bit better what you meant by *"Don't pass unbound instance methods to callbacks"*? – SrAxi May 23 '17 at 07:44
  • 1
    @SrAxi if you someone changes the callback so that it uses `this`, the program will break without warning. So if you pass a _method_ as an argument, you must bind it first `setTimeout(this.method.bind(this))`, but using an arrow function `setTimeout(() => this.method());`. checkout this answer https://stackoverflow.com/a/28668819/1915893 – Aluan Haddad May 23 '17 at 14:03
  • Thanks a lot! I will give it a look and review my code. ;) – SrAxi May 23 '17 at 14:09
-1

I always use this method and works fine for me, I hope it be usefull :

in my service file :

getSomething(){
    return this._http.get('YOUR-API-ENDPOINT').map(res=> res.json());
}

and in my component I use this to subscribe to Observable

return this._myServiceName.getSomething().subscribe(data =>{
  this.muyData=data;
}
Emad Dehnavi
  • 3,262
  • 4
  • 19
  • 44