Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular 4 how to wait for httpclient complete

Tags:

angular

In my application I have services (that are making calls to the rest service) that are inherited from the custom base service that is defining common methods and properties that are used across all other services. One of such parameters was base url for my rest service which was using a variable under environment. However now came in requirement that makes this url coming from the config file. So I would have to get it using httpclient, which presents an issue that now at the time when actual service is trying to use base url it is not yet resolved. Is there a way how to wait till that call resolves? My code looks like:

base-service.service.ts:

export class BaseService{
    protected apiUrlBase: string;

    constructor(protected _http: HttpClient) {
        // some logic

        this.getConfigData()
          .subscribe(
            x => {
              this.apiUrlBase = x.apiUrlBase;
            }
          );

    }

    getConfigData(): Observable<any> {
        return this._http.get(
          '../../assets/config.json'
        )
          .do(x => {
          })
          .catch(error => {
            alert('Unable to read configuration file:' + JSON.stringify(error));
            return Observable.throw(error);
          });
      }
}

some-data.service.ts:

@Injectable()
export class TestService extends BaseApiService {
    private get _someDataServiceUrl(): string {
        return this.apiUrlBase + '/api/v1/somedata';
    }

    constructor(_http: HttpClient) {
        super(_http);
    }

    getSomeData(): Observable<ISomeData[]> {
        return this._http.get<ISomeData[]>(this._someDataServiceUrl)
          .do(this.handleSuccessResponse)
          .catch(this.handleErrorResponse);
    }
}

Any suggestions on how I can wait in my service till apiUrl is getting resolved, or what should I do to be able to use my baseservice as well as use the data that child services are relying on to be resolved till they use it?

like image 742
AlexanderM Avatar asked Nov 10 '17 20:11

AlexanderM


2 Answers

A quick and dirty is to make the URL base an observable itself and make sure nothing in the service can continue until it gives a value:

export class BaseService{
protected apiUrlBase: BehaviorSubject<string> = new BehaviorSubject<string>(null);
protected apiUrlBase$: Observable<string> = this.apiUrlBase.filter(b => !!b).take(1);

constructor(protected _http: HttpClient) {
    // some logic

    this.getConfigData()
      .subscribe(
        x => {
          this.apiUrlBase.next(x.apiUrlBase);
        }
      );

}

getConfigData(): Observable<any> {
    return this._http.get(
      '../../assets/config.json'
    )
      .do(x => {
      })
      .catch(error => {
        alert('Unable to read configuration file:' + JSON.stringify(error));
        return Observable.throw(error);
      });
  }
} 


@Injectable()
export class TestService extends BaseApiService {
  private _someDataServiceUrl(apiUrlBase): string {
      return apiUrlBase + '/api/v1/somedata';
  }

  constructor(_http: HttpClient) {
      super(_http);
  }

  getSomeData(): Observable<ISomeData[]> {
      return this.apiUrlBase$.switchMap(apiUrlBase => this._http.get<ISomeData[]>(this._someDataServiceUrl(apiUrlBase))
        .do(this.handleSuccessResponse)
        .catch(this.handleErrorResponse));
  }
}

But this is clearly very ugly. A better solution would be to make sure this base service loads and resolves before anything else in the application by creating an app initializer provider for it:

import { Injectable, APP_INITIALIZER } from '@angular/core';

@Injectable()
export class BaseService{
  protected apiUrlBase: string;

  constructor(protected _http: HttpClient) {
  }

  getConfigData(): Observable<any> {
    return this._http.get(
      '../../assets/config.json'
    )
      .do(x => {
      })
      .catch(error => {
        alert('Unable to read configuration file:' + JSON.stringify(error));
        return Observable.throw(error);
      });
  }

  public load() {
     return new Promise((resolve, reject) => {
       this.getConfigData()
         .subscribe(
           x => {
             this.apiUrlBase = x.apiUrlBase;
             resolve(true);
           },
           err => resolve(err)
         );
     });
  }
}

export function BaseServiceInitFactory(baseService: BaseService) {
  return () => baseService.load();
}

export const BaseServiceInitProvider = {
  provide: APP_INITIALIZER,
  useFactory: BaseServiceInitFactory,
  deps: [BaseService],
  multi: true
}

then put that BaseServiceInitProvider right after your BaseService in your app module providers.

like image 91
bryan60 Avatar answered Oct 21 '22 05:10

bryan60


Async/await may be one way to implement this. The constructor calls the async function "setApiUrlBase", which waits for "getConfigData" to resolve the HTTP response of config file before setting the API Url. My personal opinion is that you should make your REST endpoint fixed so you do not need to make two HTTP requests everytime when one is sufficient, but it depends on how you want to implement it.

export class BaseService {
    protected apiUrlBase: string;

    constructor(protected _http: HttpClient) {
        this.setApiUrlBase();
    }

    async setApiUrlBase() {
        await this.getConfigData().then(
            response => {
                // Promise successful
                response.subscribe(
                    x => {
                        // Subscription successful
                        this.apiUrlBase = x.apiUrlBase;
                    }
                )
            },
            err => { 
                // Promise fail error handling
            }
        );
    }

    getConfigData(): Promise<Observable<Response>> {
        return new Promise(resolve => {
            let r : any;
            this._http.get('../../assets/config.json').subscribe(
                response => {
                    r = response;
                },
                error => {
                    // Config error handling
                },
                () => {
                    resolve(r);
                }
            )
        });
    }
}
like image 2
Daniel H.J. Avatar answered Oct 21 '22 05:10

Daniel H.J.