3

I have a module (with @NgModule) and I need to import a module:

MqttModule.forRoot(environment.MQTT_SERVICE_OPTIONS)

The problem is that I want to get the value not from the environment but from a configuration file.

I've created a service that loads the properties from a config file but I don't know how to use it in the module definition class. I'm using it in some components and everything is working great (just injecting through the constructor), but here I don't have a context for that.

Leandro
  • 870
  • 2
  • 13
  • 27

1 Answers1

2

I've created a service that loads the properties from a config file but I don't know how to use it in the module definition class.

You can't use an Angular service, because that requires the injector to be initialized. The ngModule needs to be configured before anything in Angular is started.

The root module is loaded by the plaformBrowserDynamic().bootstrapModule(AppModule) line of code in your main.ts file. This is where everything in Angular gets started.

You just need to load your configuration before this line of code is executed, and there is no technical problems delaying this operation. An asynchronous operation at this step in the bootstrap will show a blank page until the operation is complete, and this makes for a poor user experience.

I can demonstrate the effect by changing main.ts to the following:

(function () {
    return new Promise(resolver => {
        window.setTimeout(() => resolver(), 5000);
    });
})().then(() => {
    platformBrowserDynamic().bootstrapModule(AppModule)
        .catch(err => console.log(err));
});

The above does nothing but delay the bootstrapping of Angular for 5 seconds.

It's a good reason why you shouldn't load resources asynchronously when the app is starting, but if you really want to do it there is no reason why it wouldn't work.

You'll need to change the promise so that it resolves to the configuration settings you need, and then pass this to a static function in your AppModule.

I'll update my example, but imagine your final source code is making an HTTP request for the remote configuration file.

(function () {
    return new Promise(resolver => {
        const exampleConfig = {
            options: 'ABC'
        };
        window.setTimeout(() => resolver(exampleConfig), 5000);
    });
})().then(config => {
    platformBrowserDynamic().bootstrapModule(AppModule.withConfig(config))
        .catch(err => console.log(err));
});

You'll now have to create the module definition manually for the AppModule, but this works exactly the same for how modules implement forRoot() methods. I've just renamed it to withConfig() so it sounds better.

@NgModule()
export class AppModule {
    static withConfig(config: any): ModuleWithProviders {
        return {
            ngModule: AppModule,
            imports: [
                MqttModule.forRoot(config)
            ]
        };
    }
}

I strongly recommend that the back-end server inject the required configuration data into the index.html so that there is no need for an async operation. You could then reference a global variable in the AppModule and skip the withConfig() step. The server should be responsible for the bootstrap state of the Angular application.

Reactgular
  • 52,335
  • 19
  • 158
  • 208
  • 1
    I am trying to implement this, but get a TS error in AppModule that it's not assignable - `Object literal may only specify known properties, and 'imports' does not exist in type 'ModuleWithProviders'.` Do you know what can be done? Thanks! – Basya Rosemann Feb 26 '20 at 09:41