Our Angular 12 app has a module that imports a dependency we want to configure based on a configuration file that's only available in runtime, not in compile-time.
The package in question is ng-intercom, although I imagine the same issue could come up later with other packages as well.
The motivation behind the dynamic configuration is that our app runs in 4 different environments and we don't want to create separate builds for each, since the only difference between them is the configuration file which contains the backend's URL and a few Application IDs (like Intercom, Facebook app ID, etc.)
This is how the import in question is made currently:
imports: [
...
IntercomModule.forRoot({
appId: env.intercomID,
updateOnRouterChange: true,
}),
...
The issue is that appID should be configurable, the env variable should be loaded dynamically. Currently, it's a JSON imported and compile into the code, but that means we can't change it for our different environments without rebuilding the code for each:
import env from '../../assets/environment.json';
We have an APP_INITIALIZER, however, that doesn't stop modules from being imported before it's resolved:
{
provide: APP_INITIALIZER,
useFactory: AppService.load,
deps: [AppService],
multi: true,
},
... and the relevant configuration loader:
static load(): Promise<void> {
return import('../../assets/environment.json').then((configuration) => {
AppService.configSettings = configuration;
});
}
We can use this configuration without issues with our components and services.
We managed to achieve the results we want in angularx-social-login's configuration:
providers: [
...
{
provide: 'SocialAuthServiceConfig',
useValue: new Promise(async resolve => {
const config = await AppService.config();
resolve({
autoLogin: true,
providers: [
{
id: FacebookLoginProvider.PROVIDER_ID,
provider: new FacebookLoginProvider(config.facebookApi),
}
]
} as SocialAuthServiceConfig);
...
]
SocialAuthServiceConfig is a provider however, we couldn't find a way to configure ng-intercom's import similarly.
Can we achieve this somehow? Is there a way to dynamically configure module imports?
I think there is no need to configure the module imports dynamically to achieve that, instead, you can do the following:
IntercomModule
without the forRoot
function.IntercomConfig
class using useFactory
function that read the data config from the AppService.configSettings
: providers: [
{
provide: IntercomConfig,
useFactory: intercomConfigFactory,
},
],
// ....
export function intercomConfigFactory(): IntercomConfig {
return {
appId: AppService.configSettings.intercomAppId,
updateOnRouterChange: true,
};
}
most library authors provide a way to initialize the library later. The forRoot
call can be faked. If you need to configure intercom, you can still call forRoot
but you can use empty id:
IntercomModule.forRoot({
appId: null,
updateOnRouterChange: true,
}),
Then you can call boot with app_id
which is then used.
// AppComponent
constructor(private appService: AppsService, private intercom: Intercom) {
this.startIntercom();
}
private async startIntercom() {
const config = this.appService.config();
this.intercom.boot({app_id: config.intercom_app_id});
}
In general you can learn a lot by reading the library source code.
Most libraries provide methods similar to intercom.boot
.
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