【问题标题】:Dynamic configuration for Angular module importsAngular 模块导入的动态配置
【发布时间】:2021-11-12 01:11:37
【问题描述】:

我们的 Angular 12 应用有一个模块,该模块导入我们想要基于 配置文件 配置的依赖项,该文件仅在运行时可用,而不是在编译时。

有问题的包是ng-intercom,尽管我想以后其他包也会出现同样的问题。

动态配置背后的动机是我们的应用程序在 4 个不同的环境中运行,我们不想为每个环境创建单独的构建,因为它们之间的唯一区别是包含后端 URL 和一些应用程序的配置文件ID(如对讲机、Facebook 应用 ID 等)

目前有问题的 import 是这样进行的:

imports: [
  ...
  IntercomModule.forRoot({
    appId: env.intercomID,
    updateOnRouterChange: true,
  }),
  ...

问题是 appID 应该是可配置的,env 变量应该是动态加载的。目前,它是一个导入并编译到代码中的 JSON,但这意味着我们无法在不为每个环境重新构建代码的情况下针对不同的环境更改它:

import env from '../../assets/environment.json';

我们有一个 APP_INITIALIZER,但是,它不会在解决之前阻止模块被导入:

{
  provide: APP_INITIALIZER,
  useFactory: AppService.load,
  deps: [AppService],
  multi: true,
},

...以及相关的配置加载器:

static load(): Promise<void> {
  return import('../../assets/environment.json').then((configuration) => {
    AppService.configSettings = configuration;
  });
}

我们可以使用此配置而不会对我们的组件和服务产生任何问题。

我们设法在angularx-social-login 的配置中实现了我们想要的结果:

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 是一个提供者,但是我们找不到类似配置 ng-intercom 导入的方法。

我们能以某种方式实现这一目标吗? 有没有办法动态配置模块导入?

【问题讨论】:

  • 你试过使用环境变量吗?并配置为appId: proccess.env.INTERCOM_ID!
  • 在启动 Angular 应用程序之前,您可以在运行时加载任何内容,例如 stackoverflow.com/questions/56431192/… 另请参阅 stackoverflow.com/questions/54469571/…
  • @strdr4605 我们最初计划使用环境变量而不是配置文件,但是在我们的环境中会更难配置,此时我们宁愿不拆分我们的配置在环境变量和配置文件之间。
  • @yurzui 谢谢,遗憾的是我不能让它这样工作,我将我的配置注入到 platformBrowserDynamic 的 extraProviders 参数中,但是我没有找到在@NgModule 中实际访问它的方法配置,它仍然,总是未定义。我的怀疑是 @NgModule 配置在导入到 main.ts 文件(包含 bootstrapModule() 逻辑)时实际上得到了评估,但我可能是错的。因此,我选择了其他解决方案。

标签: angular typescript intercom


【解决方案1】:

我认为不需要动态配置模块导入来实现这一点,相反,您可以执行以下操作:

  • 导入不带forRoot函数的IntercomModule
  • 使用useFactory 函数提供IntercomConfig 类,该函数从AppService.configSettings 读取数据配置:
  providers: [
    {
      provide: IntercomConfig,
      useFactory: intercomConfigFactory,
    },
  ],

// ....

export function intercomConfigFactory(): IntercomConfig {
  return {
    appId: AppService.configSettings.intercomAppId,
    updateOnRouterChange: true,
  };
}

【讨论】:

  • 只有当AppService.configSettings.intercomAppId 已经可用时,您的代码才有效。但实际上是稍后加载的 - 正如 OP 所说。
  • 对不起,我的错。我更新了我的答案以使用工厂而不是值。并且 AppService.configSettings.intercomAppId 应该在工厂内可用,因为它已经在 APP_INITIALIZER 承诺中分配
  • @Amer 谢谢您,您的解决方案运行良好,我们也使用它来配置我们的其他依赖项,例如 angularx-social-login 和 angular-google-tag-manager!
【解决方案2】:

大多数库作者都提供了一种稍后初始化库的方法。 forRoot 电话可以伪造。如果需要配置对讲,仍然可以拨打forRoot,但可以使用空id:

  IntercomModule.forRoot({
    appId: null,
    updateOnRouterChange: true,
  }),

然后您可以使用app_id 调用boot。

 // 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});
 }

通常,您可以通过阅读库源代码学到很多东西。 大多数库都提供类似于intercom.boot 的方法。

【讨论】:

  • this.intercom.boot 不会覆盖config.appId 值,其他函数直接使用config.appId 而不读取传递给boot 方法的那个,例如loadItercom
  • 你不正确,boot 是唯一真正读取appId 的地方。直接在代码中搜索即可。
  • 我已经提到了直接使用 config.appId 的函数 :)
  • @kvetis 感谢您的提示,这个解决方案非常适合 ng-intercom!但是我接受了另一个答案,因为它可以立即用于其他模块,而无需深入研究库的源代码。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-10-22
  • 2018-12-14
  • 2020-09-12
  • 1970-01-01
  • 2016-08-03
  • 2022-12-30
  • 1970-01-01
相关资源
最近更新 更多