I'm expecting Angular to wait until my loadConfig()
function resolves before constructing other services, but it is not.
app.module.ts
export function initializeConfig(config: AppConfig){ return () => config.loadConfig(); } @NgModule({ declarations: [...] providers: [ AppConfig, { provide: APP_INITIALIZER, useFactory: initializeConfig, deps: [AppConfig], multi: true } ] }) export class AppModule {
}
app.config.ts
@Injectable() export class AppConfig { config: any; constructor( private injector: Injector ){ } public loadConfig() { const http = this.injector.get(HttpClient); return new Promise((resolve, reject) => { http.get('http://mycoolapp.com/env') .map((res) => res ) .catch((err) => { console.log("ERROR getting config data", err ); resolve(true); return Observable.throw(err || 'Server error while getting environment'); }) .subscribe( (configData) => { console.log("configData: ", configData); this.config = configData; resolve(true); }); }); } }
some-other-service.ts
@Injectable() export class SomeOtherService { constructor( private appConfig: AppConfig ) { console.log("This is getting called before appConfig's loadConfig method is resolved!"); } }
The constructor of SomeOtherService
is getting called before the data is received from the server. This is a problem because then the fields in SomeOtherService
do not get set to their proper values.
How do I ensure SomeOtherService
's constructor gets called only AFTER the loadConfig
's request is resolved?
I had also a simmilar issue what solved the issue for me was to use Observable methods and operators to do everything. Then in the end just use the toPromise
method of the Observable
to return a Promise
. This is also simpler because you don't need to create a promise yourself.
The AppConfig
service will then look something like that:
import { Injectable, Injector } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { Observable } from 'rxjs/Observable'; import { tap } from 'rxjs/operators/tap'; @Injectable() export class AppConfig { config: any = null; constructor( private injector: Injector ){ } public loadConfig() { const http = this.injector.get(HttpClient); return http.get('https://jsonplaceholder.typicode.com/posts/1').pipe( tap((returnedConfig) => this.config = returnedConfig) ).toPromise(); //return from([1]).toPromise(); } }
I'm using the new pipeable operators in rxjs which is recommended by Google for Angular 5. The tap
operator is equivalent to the old do
operator.
I have also created a working sample on stackblitz.com so you can se it working. Sample link
async loadConfig() { const http = this.injector.get(HttpClient); const configData = await http.get('http://mycoolapp.com/env') .map((res: Response) => { return res.json(); }).catch((err: any) => { return Observable.throw(err); }).toPromise(); this.config = configData; }); }
The await operator is used to wait for a Promise. It can only be used inside an async function.
It is working fine.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With