网站搜索

TypeScript - Angular 动态配置


所有人,

我有一个关于如何为我的 Angular Web 应用程序提供一些运行时配置选项的问题。以下是详细信息,但我想要的是能够只编译一次我的 Angular Web 应用程序,然后将其部署到多个环境中,并且只需更改端点。

我发现的是,一旦将下面的 JSON 文件导入到 app.module.ts 文件中并引用,在编译时,值会被硬编码到应用程序中,因此当我将应用程序移动到我们的测试服务器并尝试运行应用程序时,一旦 MSAL 认证发生,它会重定向回错误的 Uri。

基本上它不是在运行时读取 auth-config.json 文件,而是在编译时读取。有没有办法让它在运行时读取?

我正在使用微软提供的用于 MSAL/Azure AD 认证的包。

它使用一个名为 auth-config.json 的 json 文件。这里是其中的一小段

{
    "configuration": {
        "redirectUri": "https://SomeWebSiteName-D.domainname.com",
        "postLogoutRedirectUri": " https://SomeWebSiteName-D.domainname.com "
    },
    "apiBaseUrl": "https://SomeWebSiteNameAPI-D.domainname.com/api/"
}

这个 JSON 文件像这样被导入到 app.module.ts 文件中。

import * as auth from '../assets/config/auth-config.json';

然后它被用来配置 MSAL 包。这是引用其中一些数据的函数之一。

export function MSALInstanceFactory(): IPublicClientApplication {
  return new PublicClientApplication({
    auth: {
      clientId: auth.credentials.clientId,
      authority: 'https://login.microsoftonline.com/' + auth.credentials.tenantId,
      redirectUri: auth.configuration.redirectUri
    },
    cache: {
      cacheLocation: BrowserCacheLocation.LocalStorage,
      storeAuthStateInCookie: isIE, // set to true for IE 11
    },
  });
}

稍后在 App.module.ts 文件中,上述函数在 providers 部分被引用

@NgModule
  providers: [
    {
      provide: MSAL_INSTANCE,
      useFactory: MSALInstanceFactory,
    }

答案#1

Angular 提供了一个关于如何管理特定环境变量的解决方案。

你在某处有一个 environments 文件夹,其中有两个文件。在这里你可以指定这样的导入。根据你的 angular.json 文件,如果你的环境发生变化,这些文件将会自动被替换(Angular 文档)。

所以这里就是你应该写它的地方:

// environment.ts
export const environment = {
    "configuration": {
        "redirectUri": "https://SomeWebSiteName-D.domainname.com",
        "postLogoutRedirectUri": " https://SomeWebSiteName-D.domainname.com "
    },
    "apiBaseUrl": "https://SomeWebSiteNameAPI-D.domainname.com/api/"
}

// environment.<specific_environment>.ts
export const environment = {
    "configuration": {
        "redirectUri": "different keys",
        "postLogoutRedirectUri": "..."
    },
    ...
}

// msal-instance.factory.ts
import {environment} from '../path/to/environment';
environment.configuration;

更新:根据你的评论请求提供一个例子

要在运行时更改环境,你有多种选择。我将在这里概述一个例子;

interface Environment {
  configuration: {
    redirectUri: string,
    postLogoutRedirectUri: string,
  },
  apiBaseUrl: string;
}

const environments: Record<string, Environment> = {
  production: {
    "configuration": {
      "redirectUri": "https://SomeWebSiteName-D.domainname.com",
      "postLogoutRedirectUri": "https://SomeWebSiteName-D.domainname.com"
    },
    "apiBaseUrl": "https://SomeWebSiteNameAPI-D.domainname.com/api/"
  }
}

export let environment: Environment;

export const updateEnvironment = (env: string) => {
  environment = environments[env];
}

// 'production' may be an environment variable instead of directly being a string
updateEnvironment('production');

答案#2

以下是你可以遵循的步骤

  • 使用 JSON 文件来存储配置
  • 读取 json 文件内容
  • 使用配置

配置服务

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Configuration } from './configuration';
@Injectable({
  providedIn: 'root'
})
export class ConfigurationService {
  private configData: Configuration | undefined;
  private readonly configPath: string = '/assets/config/configuration.json';
  constructor(
    private http: HttpClient
  ) { }

  async loadConfiguration(): Promise {
    try {
      const response = await this.http.get(`${this.configPath}`)
        .toPromise();
      this.configData = response;
      return this.configData;
    } catch (err) {
      return Promise.reject(err);
    }
  }

  get config(): Configuration | undefined {
    return this.configData;
  }
}

应用模块

import { BrowserModule } from '@angular/platform-browser';
import { APP_INITIALIZER, Injector, NgModule } from '@angular/core';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { Configuration } from './config/configuration';
import { ConfigurationService } from './config/configuration.service';
import { HttpClientModule } from '@angular/common/http';

export function ConfigLoader(injector: Injector): () => Promise {
  return () => injector.get(ConfigurationService).loadConfiguration();
}

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    HttpClientModule,
    AppRoutingModule
  ],
  providers: [{
    provide: APP_INITIALIZER,
    useFactory: ConfigLoader,
    deps: [Injector],
    multi: true
  }],
  bootstrap: [AppComponent]
})
export class AppModule { }

以下文章会详细解释这一点。

https://ciemasen.com/articles/how-to-use-angular-dynamic-configuration