32

Typescript: 2.2.0 Angular: 4.0

I am attempting to ensure that a ConfigService object is initialized before application startup through the use of APP_INITIALIZER. I've found many examples of how to do this however NONE of them seem to be delaying the initialization of the app. Here are just a handful of examples I've attempted to implement.

https://github.com/angular/angular/issues/9047 https://gist.github.com/fernandohu/122e88c3bcd210bbe41c608c36306db9 Angular2 APP_INITIALIZER not consistent

Here is my NgModule class

export function init(config: ConfigService) {
  return () => {
    config.load();
  };
}


@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule,
    AppRoutingModule
  ],
  providers: [
    {
      'provide': APP_INITIALIZER,
      'useFactory': init,
      'deps': [ConfigService],
      'multi': true
    },
    ConfigService
  ],
  bootstrap: [AppComponent]
})
export class AppModule {
}

And here is the ConfigService class

@Injectable()
export class ConfigService {
  private config: ApplicationConfiguration;

  get apiRoot() {
    return this.getProperty('apiRoot'); // <--- THIS GETS CALLED FIRST
  }

  constructor(private http: Http) {
  }

  load(): Promise<any> {
      console.log('get user called');
      const promise = this.http.get('./../../assets/config.json').map((res) => res.json()).toPromise();
      promise.then(config => {
        this.config = config;     // <--- THIS RESOLVES AFTER
        console.log(this.config);
      });
    return promise;
  }

  private getProperty(property: string): any {
    //noinspection TsLint
    if (!this.config) {
      throw new Error(`Attempted to access configuration property before configuration data was loaded, please double check that 'APP_INITIALIZER is properly implemented.`);
    }

    if (!this.config[property]) {
      throw new Error(`Required property ${property} was not defined within the configuration object. Please double check the 
      assets/config.json file`);
    }

    return this.config[property];
  }
}

And to test everything I've injected ConfigService into AppComponent with this.

import { Component } from '@angular/core';
import {ConfigService} from './services/config.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent {
  title = 'app works!';
  fullImagePath = '/src/image/avatar.jpeg';


  constructor(private config: ConfigService) {
    config.apiRoot;
  }
}
Community
  • 1
  • 1
DynaWeb
  • 605
  • 1
  • 7
  • 15

1 Answers1

25

Looks like you forgot to return value from factory:

export function init(config: ConfigService) {
  return () => {
    return config.load(); // add return
  };
}

or the same code can be written a bit shortly:

export function init(config: ConfigService) {
   return () => config.load();
}
yurzui
  • 205,937
  • 32
  • 433
  • 399
  • 3
    For others reading this, theres also a more recent (as of now) https://juristr.com/blog/2018/01/ng-app-runtime-config/#runtime-configuration – redfox05 Feb 21 '18 at 00:02
  • *typo: more recent solution/approach – redfox05 Mar 15 '18 at 19:07
  • 15
    There's a subtle bug in this. While the app will not bootstrap until your config loads as determined via config.load(), however your ConfigService WILL be available as a service to inject into other services long before config.load() completes. Yup, all those other services you wrote that you inject ConfigService into will be injected with an incomplete / not yet loaded object. So, you need to be very careful how the rest of your app uses the ConfigService, because you're dealing with an object that hasn't really finished constructing. Dont rely on getting lucky with injector/factory ordering. – goat Apr 10 '18 at 00:56
  • 1
    @goat do you have any examples/advice to offer for a better approach? – Joel Davey Apr 10 '18 at 20:19
  • 4
    @JoelDavey Vote on my [feature request](https://github.com/angular/angular/issues/23279) so the ng team gives us a solution. I requested async providers, but we'll see what they do. – goat May 16 '18 at 04:11
  • Yoy can load the settings BEFORE bootstrapping the Module. Inside your App.module/other components you can access your settings without having to fear the settings are not loaded. I have created an example of how to accomplish this: https://github.com/dtiemstra/AngularConfigDemo – DiederikTiemstra Feb 17 '20 at 12:20
  • Or just: export function init(config: ConfigService) { return config.load; } – Rodrigo Pereira Fraga Feb 17 '20 at 22:09
  • @RodrigoPereiraFraga no, that is not the same thing at all – JeneralJames Jul 07 '22 at 21:32