38

I have a Angular 5.2.0 application. I looked up how to implement APP_INITIALIZER to load configuration information before the app starts. Here an extract of the app.module:

providers: [
    ConfigurationService,
    {
        provide: APP_INITIALIZER,
        useFactory: (configService: ConfigurationService) =>
            () => configService.loadConfigurationData(),
        deps: [ConfigurationService],
        multi: true
    }
],

Here the configuration.service:

import { Injectable, Inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';

import { Configuration } from './configuration';

@Injectable()
export class ConfigurationService {
    private readonly configUrlPath: string = 'Home/Configuration';
    private configData: Configuration;

    constructor(
        private http: HttpClient,
        @Inject('BASE_URL') private originUrl: string) { }

    loadConfigurationData() {
        this.http
            .get<Configuration>(`${this.originUrl}${this.configUrlPath}`)
            .subscribe(result => {
                this.configData = {
                    test1ServiceUrl: result["test1ServiceUrl"],
                    test2ServiceUrl: result["test2ServiceUrl"]        
                }
            });
    }

    get config(): Configuration {
        return this.configData;
    }
}

Here is an example of a constructor of a component where the configData is used:

export class TestComponent {
    public test1ServiceUrl: string;

    constructor(public configService: ConfigurationService) {
        this.test1ServiceUrl = this.configService.config.test1ServiceUrl;
    }
}

It works fine with all the components which are defined within the <router-outlet></router-outlet>. But the same implementation in a component outside the <router-outlet></router-outlet> does not work.
When I debug the respective constructor of the component where it does not work it says that configService is null.
Why is the APP_INITIALIZER executed before the constructor of a component inside the <router-outlet></router-outlet> is called but not before the constructor of a component outside the <router-outlet></router-outlet>?

1
  • 1
    I just discovered the APP_INITIALIZER on another thread and I'm mad that after 6 years of working with angular I haven't known about it. It's going to solve a lot of problems I have. But, the Angular docs provide absolutely zero information on it. I opened up a request with the angular team to get some info on it (github.com/angular/angular/issues/34703). Commented Jan 9, 2020 at 17:42

3 Answers 3

42

Due to how APP_INTIALIZER works, it's expected that asynchronous initializers return promises, but your implementation of APP_INTIALIZER multiprovider doesn't because loadConfigurationData function doesn't return anything.

It should be something like:

loadConfigurationData(): Promise<Configuration> {
  return this.http.get<Configuration>(`${this.originUrl}${this.configUrlPath}`)
  .do(result => {
    this.configData = result;
  })
  .toPromise();
}
Sign up to request clarification or add additional context in comments.

6 Comments

It says: (TS) Property 'do' does not exist on type 'Observable<Configuration>
This is common Angular/RxJS knowledge that isn't specific to this case. If you use RxJS operator, you need to import it first. You likely did this for other operators. There should be something like import 'rxjs/add/operator/do. See stackoverflow.com/questions/42376972/…
One important 'safety tip'. You really MUST return a Promise... trying to return the observable directly does not work and it doesn't tell you that it won't work, it just... doesn't work.
pipe/tap should be used now, instead of do.
+ lastValueFrom(...) instead of (...).toPromise()
|
5

Do something like

 export function StartupServiceFactory(startupService: StartupService) {
      return () => startupService.load();
    }
    const APPINIT_PROVIDES = [
      StartupService,
      {
        provide: APP_INITIALIZER,
        useFactory: StartupServiceFactory,
        deps: [StartupService],
        multi: true,
      },
    ];

Startup service

load():Promise{
    return new Promise(resolve, reject){
        //load all your configuration 
         resolve(); 
    }

  }

Comments

-3

I had a similar problem. The fix was much the same, actually return a promise. see: ngOnInit starts before APP_INITIALIZER is done

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.