angular-configuration-service icon indicating copy to clipboard operation
angular-configuration-service copied to clipboard

avoid using HttpClient in APP_INITIALIZER

Open tramora opened this issue 7 years ago • 3 comments

Hi @rfreedman,

Many thanks for this clear and useful project.

This implementation worked most of the times for me. However in some cases (when reloading a page for example) I got a subtle bug : the app is not blocked and components or services tried to fetch the conf before it was ready.

It was due to a HttpInterceptor implementation in the project that is called whenever a http call is done (by HttpClient). Sébastien Ollivier warns about it in his blog (https://sebastienollivier.fr/blog/angular/pourquoi-il-ne-faut-pas-utiliser-httpclient-dans-appinitializer) -- in french

His recommended fix is always to use XMLHttpRequest instead of HttpClient to avoid such issues. This fixed my issue. If you agree I could create a PR

// copied (and slightly modified) from https://github.com/rfreedman/angular-configuration-service

export function startupConfigServiceInitializerFactory(configurationService: StartupConfigService): Function {
  // a lambda is required here, otherwise `this` won't work inside StartupConfigService::load
  return () => configurationService.load();
}

@Injectable()
export class StartupConfigService {

  private loaded = false;
  private configuration: any;
  private config_folder_relative_path = 'assets/startup-config';

  public getConfig(key: any): any {

    if (!this.loaded) {
      throw new Error('StartupConfigService.getConfig() - service has not finished loading the config. It should never occur.');
    }

    return this.configuration[key];
  }

  // the return value (Promise) of this method is used as an APP_INITIALIZER,
  // so the application's initialization will not complete until the Promise resolves.
  public load(): Promise<any> {

    if (this.loaded) {
      return Observable.of(this, this.configuration).toPromise();
    } else {
      return new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest();
        xhr.open('GET', `${this.config_folder_relative_path}/ui-config.json`);

        xhr.addEventListener('readystatechange', () => {
          if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
            this.configuration = JSON.parse(xhr.responseText);
            console.log(`configuration fetched : ${JSON.stringify(this.configuration)}`);
            this.loaded = true;
            resolve(this.configuration);
          } else if (xhr.readyState === XMLHttpRequest.DONE) {
            reject();
          }
        });
        xhr.send(null);
      });
    }
  }

}

tramora avatar Aug 01 '18 13:08 tramora

Thanks, great catch! Looks like a really good wat to fix the issue - I will definitely try it out.

On Wed, Aug 1, 2018 at 9:53 AM Thierry R [email protected] wrote:

Hi @rfreedman https://github.com/rfreedman,

Many thanks for this clear and useful project.

This implementation worked most of the times for me. However in some cases (when reloading a page for example) I got a subtle bug : the app is not blocked and components or services tried to fetch the conf before it was ready.

It was due to a HttpInterceptor implementation in the project that is called whenever a http call is done (by HttpClient). Sébastien Ollivier warns about it in his blog ( https://sebastienollivier.fr/blog/angular/pourquoi-il-ne-faut-pas-utiliser-httpclient-dans-appinitializer) -- in french

His recommended fix is always to use XMLHttpRequest instead of HttpClient to avoid such issues. This fixed my issue. If you agree I could create a PR

// copied (and slightly modified) from https://github.com/rfreedman/angular-configuration-service

export function startupConfigServiceInitializerFactory(configurationService: StartupConfigService): Function { // a lambda is required here, otherwise this won't work inside StartupConfigService::load return () => configurationService.load(); }

@Injectable() export class StartupConfigService {

private loaded = false; private configuration: any; private config_folder_relative_path = 'assets/startup-config';

public getConfig(key: any): any {

if (!this.loaded) {
  throw new Error('StartupConfigService.getConfig() - service has not finished loading the config. It should never occur.');
}

return this.configuration[key];

}

// the return value (Promise) of this method is used as an APP_INITIALIZER, // so the application's initialization will not complete until the Promise resolves. public load(): Promise {

if (this.loaded) {
  return Observable.of(this, this.configuration).toPromise();
} else {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    xhr.open('GET', `${this.config_folder_relative_path}/ui-config.json`);

    xhr.addEventListener('readystatechange', () => {
      if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
        this.configuration = JSON.parse(xhr.responseText);
        console.log(`configuration fetched : ${JSON.stringify(this.configuration)}`);
        this.loaded = true;
        resolve(this.configuration);
      } else if (xhr.readyState === XMLHttpRequest.DONE) {
        reject();
      }
    });
    xhr.send(null);
  });
}

}

}

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/rfreedman/angular-configuration-service/issues/1, or mute the thread https://github.com/notifications/unsubscribe-auth/AAOWkKyFre2IJzPUEh6tmcur3RNcrjQAks5uMbK9gaJpZM4VqipJ .

rfreedman avatar Aug 01 '18 15:08 rfreedman

Thanks and i love u;

alTach avatar Jul 07 '19 17:07 alTach

Thanks for the tip here, I ran into this issue with the Interceptors relying on a service that needed the config request to be resolved already. Changing to XHR solved it.

I suspect it's possible to use Angular's HttpBackend here as well (call the handle method of http instead of request) as HttpBackend is a level lower than the interceptors, so interceptors shouldn't get initialized when that method is used. I haven't tested this though, but would be an easy swap in case anybody wants to keep using Angular provided services.

jaydiablo avatar Jan 20 '20 20:01 jaydiablo