AppModule加载之前的角加载外部配置

Den*_*ing 6 typescript angular

考虑以下情形(Angular v7):

  1. 在加载AppModule 之前,从外部端点(JSON)加载配置参数(API服务器URL和Auth服务器URL)
  2. 将配置传递给AppModule(OAuth2模块)
  3. 使用AOT编译应用

第二点是这里的关键,看起来像这样:

@NgModule({
  imports: [
    ...
    OAuthModule.forRoot({
      resourceServer: {
        allowedUrls: [API_SERVER_URL], // <== we need to set the value that we loaded from the external endpoint (JSON) here
        sendAccessToken: true
      }
    }),
    ...
  ],
  declarations: [AppComponent],
  bootstrap: [AppComponent]
})
export class AppModule { }
Run Code Online (Sandbox Code Playgroud)

我已经尝试了以下内容:

  • 使用APP_INITIALIZER的解决方案。这是行不通的,因为OAuthModule.forRoot()会在APP_INITIALIZER下载外部配置JSON 之前触发。
  • 加载配置与异步功能main.ts进入角环境变量,然后引导中的AppModule。由于中的import { AppModule } from './app/app.module';语句也无法正常工作main.ts,这会导致AppModule在加载OAuthModule.forRoot()外部配置之前加载并启动(此注释确认了此行为)。
  • 加载的AppModule动态main.ts,所以没有import在上面的语句。是该注释中给出的StackBlitz示例。它可以工作,但是1)打破了延迟加载,WARNING in Lazy routes discovery is not enabled.并且2)不适用于AOT编译。它确实非常接近我的需求。

想知道是否有人知道在加载AppModule 之前加载外部配置的另一种方法。

选项3的StackBlitz(动态加载AppModule):https ://stackblitz.com/edit/angular-n8hdty

yur*_*zui 6

Angular文档中有一章非常棒,称为NgModule FAQs,其中包含以下部分:

如果两个模块提供相同的服务怎么办?

...

如果NgModule A为令牌“ X”提供服务,并导入NgModule B也为令牌“ X”提供服务,则NgModule A的服务定义“获胜”。

换句话说,您可以在AppModule中为您的库重写OAuthModuleConfig:

主要

(async () => {
  const response = await fetch('https://api.myjson.com/bins/lf0ns');
  const config = await response.json();

  environment['allowedUrls'] = config.apiBaseURL;

  platformBrowserDynamic().bootstrapModule(AppModule)
    .catch(err => console.error(err));
})();
Run Code Online (Sandbox Code Playgroud)

app.module.ts

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppComponent } from './app.component';
import { OAuthModule, OAuthModuleConfig } from 'angular-oauth2-oidc';
import { HttpClientModule } from '@angular/common/http';
import { environment } from '../environments/environment';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    HttpClientModule,
    OAuthModule.forRoot(),
  ],
  providers: [
    {
      provide: OAuthModuleConfig,
      useFactory: () => ({
        resourceServer: {
          allowedUrls: [environment['allowedUrls']],
          sendAccessToken: true
        }
      })
    }
  ],
  bootstrap: [AppComponent]
})
export class AppModule {}
Run Code Online (Sandbox Code Playgroud)

请注意,我们也应该使用useFactory代替,useValue这样我们就不必依赖何时AppModule导入。

  • 你是英雄!感谢您的明确解释。这可以解决问题,我只需要做些小改动就可以使AOT工作。将在单独的答案中发布。 (2认同)

Den*_*ing 5

除了@yurzui 的回答,如果你在 AOT 中尝试这个(例如ng build --prod),你会得到

ERROR in Error during template compile of 'AppModule' 'AuthModule' 中的装饰器不支持函数表达式 'AuthModule' 包含位于 src\app\core\auth.module.ts(29,23) 的错误考虑将函数表达式更改为导出的函数。

所以我们为工厂创建了一个导出函数:

app.module.ts

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppComponent } from './app.component';
import { OAuthModule, OAuthModuleConfig } from 'angular-oauth2-oidc';
import { HttpClientModule } from '@angular/common/http';
import { environment } from '../environments/environment';

export function oAuthConfigFactory() : OAuthModuleConfig {
  return {
    resourceServer: {
      allowedUrls: [environment.servers.apiServer],
      sendAccessToken: true
    }
  }
}

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    HttpClientModule,
    OAuthModule.forRoot(),
  ],
  providers: [
    {
      provide: OAuthModuleConfig,
      useFactory: oAuthConfigFactory
    }
  ],
  bootstrap: [AppComponent]
})
export class AppModule {}
Run Code Online (Sandbox Code Playgroud)


Max*_*nas 5

这里的另一种选择。@yurzui 答案有效,但它需要使用useFactory它使代码更难理解。

useFactory是必需的,因为 Angular@NgModule装饰器将在AppModule导入后立即执行main.ts,因此尚未加载配置。

所以我决定通过在angular.js. 就是这样:

src/config/load.js:

// This file is added to the scripts section of 'angular.json' so it can run before Angular bootstrap process.
// It responsability is to load app configuration from JSON files.
(() => {
  const fetchSync = url => {
    // The code below will log the following warning: "[Deprecation] Synchronous XMLHttpRequest on the main thread is deprecated because of its detrimental effects to the end user's experience. For more help, check https://xhr.spec.whatwg.org/.",
    // but since we want the configuration to be set before Angular bootstrap process, we ignore this warning.
    const xhr = new XMLHttpRequest();
    xhr.open('GET', url, false);
    xhr.send(null);
    return JSON.parse(xhr.responseText);
  };

  // We attach the fetched configuration to the 'window' global variable to access it later from Angular.
  window.configuration = {
    ...fetchSync('config/config.base.json'),
    ...fetchSync('config/config.local.json'),
  };
})();
Run Code Online (Sandbox Code Playgroud)

角度.json:

  // ...
  "architect": {
    "build": {
      "builder": "@angular-devkit/build-angular:browser",
      "options": {
        // ...
        "assets": [
          // ...
          "src/config/config.base.json",
          "src/config/config.local.json"
        ],
        "scripts": ["src/config/load.js"],
  // ...
Run Code Online (Sandbox Code Playgroud)

src/config/configuration.ts:

import get from 'lodash/get';

export class Configuration {
  // We get the configuration from the 'window.configuration' property which as been set earlier by 'config/load.js'.
  private static value = (window as any).configuration;

  /**
   * Get configuration value.
   * @param path The path of the configuration value. Use '.' for nested values.
   * @param defaultValue The returned value if the given path doesn't exist.
   * @example
   * const baseUrl = Configuration.get<string>('apis.github.baseUrl');
   */
  static get<T>(path: string, defaultValue?: T): T {
    return get(Configuration.value, path, defaultValue);
  }
}
Run Code Online (Sandbox Code Playgroud)

然后你可以使用:

OAuthModule.forRoot({
  resourceServer: {
    allowedUrls: Configuration.get('allowedUrls')
    sendAccessToken: true
  }
}),
Run Code Online (Sandbox Code Playgroud)

如果您对 lodash 有问题,请查看内容。