Angular5: 将Angular应用程序部署到多个客户端

6

我正在开发一个Angular应用程序,这是一个产品,并部署到多个客户端。客户将获取此Angular应用程序并在其服务器上托管。因此,服务的URL对于每个客户端都是不同的。我看过一些解决方案,它们涉及到dev、prod。但这种情况不适用。我正在寻找类似于.NET配置文件的配置文件。我已经创建了一个单独的app.config.ts文件,但当我进行ng build时,它会被构建。我正在寻找一种解决方案,可以将应用程序部署到任何客户端而无需再次构建。

import { InjectionToken } from "@angular/core";

export let APP_CONFIG = new InjectionToken("app.config");

export interface IAppConfig {
    apiEndpoint: string;
}

export const AppConfig: IAppConfig = {    
    apiEndpoint: "http://88.87.86.999/"
};

Angular应用程序将从与服务相同的服务器和端口托管/提供吗?还是它们会不同? - Simply Ged
它们将会不同。如果它们相同,那么我就可以轻松获取托管的URL.. :) - indra257
问题在于Angular应用程序将在客户端的浏览器中运行,因此任何配置都需要发送或存储在每个客户端上。我不确定你会如何做到这一点?不幸的是,服务器地址需要在构建时知道。我唯一能想到的是将其部署为Docker容器,并在启动容器时让客户提供服务器地址作为参数。 - Simply Ged
2个回答

14
你可以将配置文件以JSON格式放在资产文件夹中,并动态检索。确保在应用程序启动之前检索它,这样当组件/服务需要时,它就可以使用。为此,您可以使用APP_INITIALIZER令牌。
步骤1:将您的JSON配置文件放置在src/assets/config/conf.json下(JSON格式,不是TS格式,因为在生产模式中没有TS编译器)。
步骤2:添加一个新的配置服务。
import {Inject, Injectable} from '@angular/core';
import {HttpClient} from "@angular/common/http";
import {Observable} from 'rxjs/Rx';
import {environment} from "../../environments/environment";

/**
 * Declaration of config class
 */
export class AppConfig
{
//Your properties here
  readonly apiEndpoint: string;
}

/**
 * Global variable containing actual config to use. Initialised via ajax call
 */
export let APP_CONFIG: AppConfig;

/**
 * Service in charge of dynamically initialising configuration
 */
@Injectable()
export class AppConfigService
{

  constructor(private http: HttpClient)
  {
  }

  public load()
  {
    return new Promise((resolve, reject) => {

      this.http.get('/assets/config/config.json').catch((error: any): any => {
        reject(true);
        return Observable.throw('Server error');
      }).subscribe((envResponse :any) => {
        let t = new AppConfig();
        //Modify envResponse here if needed (e.g. to ajust parameters for https,...)
        APP_CONFIG = Object.assign(t, envResponse);
        resolve(true);
      });

    });
  }
}

步骤 #3: 在您的主模块中,在声明模块之前添加以下内容

/**
* Exported function so that it works with AOT
* @param {AppConfigService} configService
* @returns {Function}
*/
export function loadConfigService(configService: AppConfigService): Function 

{
  return () => { return configService.load() }; 
}

第四步:修改模块提供者以添加此内容。

providers: [
  …

  AppConfigService,
  { provide: APP_INITIALIZER, useFactory: loadConfigService , deps: [AppConfigService], multi: true },


],

步骤五: 在你的代码中使用配置文件

import {APP_CONFIG} from "../services/app-config.service";

//…
return APP_CONFIG.configXXX;

现在,您可以将应用程序交付给多个客户端;每个客户端只需要在conf.json文件中有自己的特定参数。

唯一的问题是,客户端用户可以通过执行http:url/assets/config.json轻松访问conf.json文件。 - indra257
这是什么问题呢?即使配置完全打包到构建中,它仍然可以作为客户端JS代码访问。也许可以检索加密的配置JSON文件,以使值稍微难以看到,但我不确定是否有这个必要。 - David
这样做是否比将配置属性保存在环境文件中更好? - MartaGalve
如果它保存在环境文件中,那么每个客户端都必须有一个构建。采用这种方法,您可以拥有相同的构建,并将其部署到每个客户端,只需更改配置文件即可。 - David
1
感谢 @David 的详细解释。然而,在引导我们的应用程序时,使用 AppConfigService 遇到了问题,因为某些根模块需要在引导时传递设置,而该服务仅在引导之后运行。例如:AuthModule.forRoot(appConfig)。 - Yinon
显示剩余4条评论

8

为了在初始加载配置并在引导AppModule时使用它,我们在 main.ts 中使用了这个简单的解决方案。

请注意我们排队了

platformBrowserDynamic().bootstrapModule(AppModule)
    .catch(err => console.error(err));

在等待配置文件获取的异步函数内部。

(async () => {

  // console.time()
  const response = await fetch('./assets/config/conf.json');
  const json = await response.json();
  Object.entries(json).forEach(([key, value]) => {
    environment[key] = value;
  });
  // console.timeEnd()

  platformBrowserDynamic().bootstrapModule(AppModule)
    .catch(err => console.error(err));

})();

配置文件位于资源中,并通过部署任务相应替换。

这意味着我们有基于环境的配置:

  • config.json
  • config.qa.json
  • config.prod.json

对我们来说,获取时间约为40毫秒(实际上是微不足道的。取消console.time的注释以检查您自己的项目估计值)。 我怀疑在任何项目上它都不会消耗太多时间,因为它是从本地文件获取的。祝你好运!


小提示:如果您需要将这些环境变量传递到 AppModule 中的提供者,可以使用 useFactory。更多详情请参阅此处 - Dennis Ameling
我还需要在引导之前注入变量。非常感谢这个解决方案。此外,我喜欢这种方法,因为它仍然可以运行ng build --prod等命令。 - Anonymous

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接