简体   繁体   中英

Angular 4 how to wait for httpclient complete

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?

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.

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);
                }
            )
        });
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM