Angular 导入的模块不等待 APP_INITIALIZER

Sam*_*ams 16 auth0 angular

auth0/auth0-angular我正在尝试在 Angular 11 应用程序中使用该库。

我正在关注动态加载配置部分。

它提供了此示例应用程序模块代码:

// app.module.ts
// ---------------------------
import { AuthModule, AuthClientConfig } from '@auth0/auth0-angular';

// Provide an initializer function that returns a Promise
function configInitializer(
  handler: HttpBackend,
  config: AuthClientConfig
) {
  return () =>
    new HttpClient(handler)
      .get('/config')
      .toPromise()
      .then((loadedConfig: any) => config.set(loadedConfig));   // Set the config that was loaded asynchronously here
}

// Provide APP_INITIALIZER with this function. Note that there is no config passed to AuthModule.forRoot
imports: [
  // other imports..

  HttpClientModule,
  AuthModule.forRoot(),   //<- don't pass any config here
],
providers: [
  {
    provide: APP_INITIALIZER,
    useFactory: configInitializer,    // <- pass your initializer function here
    deps: [HttpBackend, AuthClientConfig],
    multi: true,
  },
],
Run Code Online (Sandbox Code Playgroud)

简而言之,它使用APP_INITIALIZER提供程序通过 a 动态加载配置Promise,这应该在 Auth0 库AuthModule实例化之前完成,以便它具有从 API 加载的适当 Auth0 配置值,并AuthClientConfig.set(...)已提前使用这些值进行调用。

Angular APP_INITIALIZER 文档说:

如果这些函数中的任何一个返回 Promise,则在解决 Promise 之前初始化不会完成。

因此,他们的例子从表面上看是有道理的。

但是,当我尝试在自己的应用程序中实际实现此解决方案时,出现以下错误:

Error: Configuration must be specified either through AuthModule.forRoot or through AuthClientConfig.set
Run Code Online (Sandbox Code Playgroud)

这表明AuthModule在加载和设置配置之前已经实例化了。

在我看来,AngularPromise在开始实例化导入模块之前实际上并没有等待解析。

我认为这个StackBlitz 演示在一个没有任何 Auth0 依赖项的简化示例中演示了该问题。

在此示例中,我希望TestModule在解决之后才实例化它Promise,因此我应该看到以下控制台输出:

Inside factory method
Inside promise
Inside timeout
TestModule constructor
Run Code Online (Sandbox Code Playgroud)

但我实际看到的是这样的:

TestModule constructor
Inside factory method
Inside promise
Inside timeout
Run Code Online (Sandbox Code Playgroud)

有人可以帮助我了解 的确切性质APP_INITIALIZER,即它何时被调用、Angular 何时等待Promise解析、Angular 何时开始实例化其他模块、为什么我的 Auth0 设置可能无法正确加载等?

Sam*_*ams 20

TL;DR - 我最终通过main.ts在引导应用程序之前加载配置来解决这个问题,然后通过自定义注入令牌使配置可用,然后我的应用程序配置服务不需要等待它通过 HTTP 加载,因为它已经可用的。

细节

我的界面片段AppConfig

export interface AppConfig {
  auth: {
    auth0_audience: string,
    auth0_domain: string,
    auth0_client_id: string,
  };
}
Run Code Online (Sandbox Code Playgroud)

我的常量文件中的自定义InjectionToken

 const APP_CONFIG: InjectionToken<AppConfig>
  = new InjectionToken<AppConfig>('Application Configuration');
Run Code Online (Sandbox Code Playgroud)

main.ts:

fetch('/config.json')
  .then(response => response.json())
  .then((config: AppConfig) => {
    if (environment.production) {
      enableProdMode();
    }

    platformBrowserDynamic([
      { provide: APP_CONFIG, useValue: config },
    ])
      .bootstrapModule(AppModule)
      .catch(err => console.error(err));
  });
Run Code Online (Sandbox Code Playgroud)

然后在我的 main 中,AppModule我导入没有配置的 Auth0AuthModule.forRoot()并调用我自己的AppConfigService来配置AuthModule.

我仍然需要APP_INITIALIZER依赖AppConfigService并返回 a ,Promise这会以某种方式让 Angular 等待AppConfigService 构造函数被调用,但它不会执行任何操作(并且仍然不会延迟AuthModule初始化),所以我立即解决它。

AppModule:

@NgModule({
  declarations: [
    ...
  ],
  imports: [
    AuthModule.forRoot(),
    ...
  ],
  providers: [
    AppConfigService,
    {
      provide: APP_INITIALIZER,
      useFactory: () => () => {
        return new Promise(resolve => {
          resolve();
        });
      },
      deps: [ AppConfigService ],
      multi: true,
    },
    {
      provide: HTTP_INTERCEPTORS,
      useClass: AuthHttpInterceptor,
      multi: true,
    },
  ],
  bootstrap: [ AppComponent ],
})
export class AppModule { }
Run Code Online (Sandbox Code Playgroud)

最后,AppConfigService

@Injectable()
export class AppConfigService {

  constructor(
    @Inject(APP_CONFIG) private readonly appConfig: AppConfig,
    private authClientConfig: AuthClientConfig,
  ) {
    this.authClientConfig.set({
      clientId: this.appConfig.auth.auth0_client_id,
      domain: this.appConfig.auth.auth0_domain,
      audience: this.appConfig.auth.auth0_audience,
      httpInterceptor: {
        allowedList: [
          ...
        ],
      },
    });
  }
}
Run Code Online (Sandbox Code Playgroud)

这一切似乎工作正常,尽管我仍然不明白其确切性质APP_INITIALIZER,并且我不太高兴在构造函数中调用 Auth0 客户端配置的set方法,而不是像文档建议的那样使用异步“加载”方法。

  • 嘿伙计,谢谢你。我花了一整天的时间试图让 APP_INITIALIZER 启动(但没有成功)。使用 main.ts 方法,我能够注入 AppConfig 类。我不需要 APP_INITIALIZER 步骤,因为配置会直接注入 Auth0AuthService,然后使用它来创建 WebAuth 客户端。 (3认同)