avoid using HttpClient in APP_INITIALIZER
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);
});
}
}
}
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
thiswon'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 .
Thanks and i love u;
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.