App.settings - Angular方式?

Roy*_*mir 66 javascript angular

我想在App Settings我的应用程序中添加一个部分,其中包含一些consts和预定义的值.

我已经阅读了这个使用的答案OpaqueToken但它在Angular中已被弃用.此文章解释了差异,但它没有提供一个完整的例子,我的尝试均告失败.

这是我尝试过的(我不知道这是不是正确的方法):

//ServiceAppSettings.ts

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

const CONFIG = {
  apiUrl: 'http://my.api.com',
  theme: 'suicid-squad',
  title: 'My awesome app'
};
const FEATURE_ENABLED = true;
const API_URL = new InjectionToken<string>('apiUrl');
Run Code Online (Sandbox Code Playgroud)

这是我想要使用这些consts的组件:

//MainPage.ts

import {...} from '@angular/core'
import {ServiceTest} from "./ServiceTest"

@Component({
  selector: 'my-app',
  template: `
   <span>Hi</span>
  ` ,  providers: [
    {
      provide: ServiceTest,
      useFactory: ( apiUrl) => {
        // create data service
      },
      deps: [

        new Inject(API_URL)
      ]
    }
  ]
})
export class MainPage {


}
Run Code Online (Sandbox Code Playgroud)

但它不起作用,我得到错误.

题:

如何以Angular方式使用"app.settings"值?

plunker

NB当然我可以创建Injectable服务并将其放在NgModule的提供者中,但正如我所说,我想用InjectionTokenAngular方式来做.

til*_*ilo 128

如果您使用的是,还有另一种选择:

Angular CLI提供环境文件src/environments(默认为environment.ts(dev)和environment.prod.ts(生产)).

请注意,您需要在所有environment.*文件中提供配置参数,例如,

environment.ts:

export const environment = {
  production: false,
  apiEndpoint: 'http://localhost:8000/api/v1'
};
Run Code Online (Sandbox Code Playgroud)

environment.prod.ts:

export const environment = {
  production: true,
  apiEndpoint: '__your_production_server__'
};
Run Code Online (Sandbox Code Playgroud)

并在您的服务中使用它们(自动选择正确的环境文件):

api.service.ts

// ... other imports
import { environment } from '../../environments/environment';

@Injectable()
export class ApiService {     

  public apiRequest(): Observable<MyObject[]> {
    const path = environment.apiEndpoint + `/objects`;
    // ...
  }

// ...
}
Run Code Online (Sandbox Code Playgroud)

Github(Angular CLI版本6)官方Angular指南(版本7)中阅读有关应用程序环境的更多信息.

  • 这在正常的软件开发中有点反模式; API网址只是配置.它不应该重新构建为不同的环境重新配置应用程序.它应该构建一次,多次部署(pre-prod,staging,prod等). (26认同)
  • 在构建之后它是可配置的吗? (6认同)
  • @MattTester这实际上是一个官方的Angular-CLI故事.如果你碰巧对这个问题有了更好的答案:随意发布! (3认同)
  • 它的工作正常.但是在移动构建时它也被改为bundle.I应该在我的服务中更改configuartion而不是在转移到生产后的代码中 (2认同)

mtp*_*ltz 46

我想出了如何使用InjectionTokens执行此操作(请参阅下面的示例),如果您的项目是使用Angular CLI您构建的,则可以使用/environments静态文件中的环境文件(application wide settings如API端点),但根据项目的要求,您很可能最终会使用两者既然环境文件只是对象文字,而使用InjectionToken's的可注入配置可以使用环境变量,因为它是一个类可以应用逻辑来根据应用程序中的其他因素配置它,例如初始http请求数据,子域等

注入令牌示例

/app/app-config.module.ts

import { NgModule, InjectionToken } from '@angular/core';
import { environment } from '../environments/environment';

export let APP_CONFIG = new InjectionToken<AppConfig>('app.config');

export class AppConfig {
  apiEndpoint: string;
}

export const APP_DI_CONFIG: AppConfig = {
  apiEndpoint: environment.apiEndpoint
};

@NgModule({
  providers: [{
    provide: APP_CONFIG,
    useValue: APP_DI_CONFIG
  }]
})
export class AppConfigModule { }
Run Code Online (Sandbox Code Playgroud)

/app/app.module.ts

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

import { AppConfigModule } from './app-config.module';

@NgModule({
  declarations: [
    // ...
  ],
  imports: [
    // ...
    AppConfigModule
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }
Run Code Online (Sandbox Code Playgroud)

现在你可以把它转换成任何组件,服务等:

/app/core/auth.service.ts

import { Injectable, Inject } from '@angular/core';
import { Http, Response } from '@angular/http';
import { Router } from '@angular/router';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch';
import 'rxjs/add/observable/throw';

import { APP_CONFIG, AppConfig } from '../app-config.module';
import { AuthHttp } from 'angular2-jwt';

@Injectable()
export class AuthService {

  constructor(
    private http: Http,
    private router: Router,
    private authHttp: AuthHttp,
    @Inject(APP_CONFIG) private config: AppConfig
  ) { }

  /**
   * Logs a user into the application.
   * @param payload
   */
  public login(payload: { username: string, password: string }) {
    return this.http
      .post(`${this.config.apiEndpoint}/login`, payload)
      .map((response: Response) => {
        const token = response.json().token;
        sessionStorage.setItem('token', token); // TODO: can this be done else where? interceptor
        return this.handleResponse(response); // TODO:  unset token shouldn't return the token to login
      })
      .catch(this.handleError);
  }

  // ...
}
Run Code Online (Sandbox Code Playgroud)

然后,您还可以使用导出的AppConfig键入检查配置.


Mat*_*ter 39

建议不要将environment.*.ts文件用于API url配置。似乎您应该这样做,因为它提到了“环境”一词。

使用此实际上是编译时配置。如果要更改API网址,则需要重新构建。那是你不想做的事情...只问你友好的质量保证部门:)

您需要的是运行时配置,即应用程序在启动时加载其配置。

其他一些答案也涉及到这一点,但不同之处在于,应用程序启动后需要立即加载配置,以便普通服务可以在需要时使用它。

要实现运行时配置:

  1. 将JSON配置文件添加到/src/assets/文件夹(以便在构建时复制)
  2. 创建一个AppConfigService以加载和分发配置
  3. 使用 APP_INITIALISER

1.将配置文件添加到 /src/assets

您可以将其添加到另一个文件夹,但是您需要告诉CLI它是中的资产angular.json。使用资产文件夹开始:

{
  "apiBaseUrl": "https://development.local/apiUrl"
}
Run Code Online (Sandbox Code Playgroud)

2.建立 AppConfigService

这是将在需要配置值时注入的服务:

{
  "apiBaseUrl": "https://development.local/apiUrl"
}
Run Code Online (Sandbox Code Playgroud)

3.使用 APP_INITIALISER

为了在AppConfigService配置完全加载的情况下安全地注入,我们需要在应用启动时加载配置。重要的是,初始化工厂函数需要返回a,Promise以便Angular知道要等到其完成解析后才能完成启动:

@Injectable({
  providedIn: 'root'
})
export class AppConfigService {

  private appConfig: any;

  constructor(private http: HttpClient) { }

  loadAppConfig() {
    return this.http.get('/assets/config.json')
      .toPromise()
      .then(data => {
        this.appConfig = data;
      });
  }

  // This is an example property ... you can make it however you want.
  get apiBaseUrl() {

    if (!this.appConfig) {
      throw Error('Config file not loaded!');
    }

    return this.appConfig.apiBaseUrl;
  }
}
Run Code Online (Sandbox Code Playgroud)

现在,您可以将其注入到任何需要的地方,所有配置都可以读取:

NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    HttpClientModule
  ],
  providers: [
    {
      provide: APP_INITIALIZER,
      multi: true,
      deps: [AppConfigService],
      useFactory: (appConfigService: AppConfigService) => {
        return () => {
          //Make sure to return a promise!
          return appConfigService.loadAppConfig();
        };
      }
    }
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }
Run Code Online (Sandbox Code Playgroud)

我说的不够强烈,将您的API网址配置为编译时配置是一种反模式。使用运行时配置。

  • 本地文件或不同的服务,编译时配置不应用于 API url。想象一下,如果您的应用程序作为产品出售(购买者安装),您不希望他们编译它,等等。无论哪种方式,您都不想重新编译 2 年前构建的东西,只是因为API 网址已更改。风险!! (5认同)
  • @CrhistianRamirez这是从应用程序的角度来看:配置直到运行时才知道,静态文件位于构建之外,可以在部署时以多种方式设置。静态文件适用于非敏感配置。API 或其他受保护的端点可以使用相同的技术,但如何进行身份验证以使其受到保护是您的下一个挑战。 (3认同)
  • @MattTester - 如果 Angular 实现了此功能,它将解决我们的问题:https://github.com/angular/angular/issues/23279#issuecomment-528417026 (2认同)
  • @DaleK 从字里行间看出,您正在使用 Web 部署进行部署。如果您使用的是部署管道(例如 Azure DevOps),那么下一步可以正确设置配置文件。配置的设置是部署过程/管道的责任,它可以覆盖默认配置文件中的值。希望能澄清。 (2认同)

Gle*_*enn 13

我发现APP_INITIALIZER在其他服务提供商要求注入配置的情况下使用 an 不起作用。它们可以在APP_INITIALIZER运行之前实例化。

我见过其他解决方案,用于读取 config.json 文件并在引导根模块之前fetch使用参数中的注入令牌提供它。platformBrowserDynamic()fetch并非所有浏览器都支持,特别是我的目标移动设备的 WebView 浏览器。

The following is a solution that works for me for both PWA and mobile devices (WebView). Note: I've only tested in Android so far; working from home means I don't have access to a Mac to build.

In main.ts:

import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

import { AppModule } from './app/app.module';
import { environment } from './environments/environment';
import { APP_CONFIG } from './app/lib/angular/injection-tokens';

function configListener() {
  try {
    const configuration = JSON.parse(this.responseText);

    // pass config to bootstrap process using an injection token
    platformBrowserDynamic([
      { provide: APP_CONFIG, useValue: configuration }
    ])
      .bootstrapModule(AppModule)
      .catch(err => console.error(err));

  } catch (error) {
    console.error(error);
  }
}

function configFailed(evt) {
  console.error('Error: retrieving config.json');
}

if (environment.production) {
  enableProdMode();
}

const request = new XMLHttpRequest();
request.addEventListener('load', configListener);
request.addEventListener('error', configFailed);
request.open('GET', './assets/config/config.json');
request.send();
Run Code Online (Sandbox Code Playgroud)

This code:

  1. kicks off an async request for the config.json file.
  2. When the request completes, parses the JSON into a Javascript object
  3. provides the value using the APP_CONFIG injection token, prior to bootstrapping.
  4. And finally bootstraps the root module.

APP_CONFIG can then be injected into any additional providers in app-module.ts and it will be defined. For example, I can initialise the FIREBASE_OPTIONS injection token from @angular/fire with the following:

{
      provide: FIREBASE_OPTIONS,
      useFactory: (config: IConfig) => config.firebaseConfig,
      deps: [APP_CONFIG]
}
Run Code Online (Sandbox Code Playgroud)

I find this whole thing a surprisingly difficult (and hacky) thing to do for a very common requirement. Hopefully in the near future there will be a better way, such as, support for async provider factories.

The rest of the code for completeness...

In app/lib/angular/injection-tokens.ts:

import { InjectionToken } from '@angular/core';
import { IConfig } from '../config/config';

export const APP_CONFIG = new InjectionToken<IConfig>('app-config');
Run Code Online (Sandbox Code Playgroud)

and in app/lib/config/config.ts I define the interface for my JSON config file:

export interface IConfig {
    name: string;
    version: string;
    instance: string;
    firebaseConfig: {
        apiKey: string;
        // etc
    }
}
Run Code Online (Sandbox Code Playgroud)

Config is stored in assets/config/config.json:

{
  "name": "my-app",
  "version": "#{Build.BuildNumber}#",
  "instance": "localdev",
  "firebaseConfig": {
    "apiKey": "abcd"
    ...
  }
}
Run Code Online (Sandbox Code Playgroud)

Note: I use an Azure DevOps task to insert Build.BuildNumber and substitute other settings for different deployment environments as it is being deployed.

  • 谢谢,虽然有问题。`configListener()` 应该是 `configListener(response: any)` 并且你应该解析 `const configuration = JSON.parse(response.target.responseText)` 因为没有 `this.responseText`。明确地说,您实际上可以在任何类(组件、服务)的构造函数中使用“@Inject(APP_CONFIG) private config: IConfig”来访问配置值。 (2认同)

小智 8

这是我的解决方案,从.json加载以允许更改而无需重建

import { Injectable, Inject } from '@angular/core';
import { Http } from '@angular/http';
import { Observable } from 'rxjs/Observable';
import { Location } from '@angular/common';

@Injectable()
export class ConfigService {

    private config: any;

    constructor(private location: Location, private http: Http) {
    }

    async apiUrl(): Promise<string> {
        let conf = await this.getConfig();
        return Promise.resolve(conf.apiUrl);
    }

    private async getConfig(): Promise<any> {
        if (!this.config) {
            this.config = (await this.http.get(this.location.prepareExternalUrl('/assets/config.json')).toPromise()).json();
        }
        return Promise.resolve(this.config);
    }
}
Run Code Online (Sandbox Code Playgroud)

和config.json

{
    "apiUrl": "http://localhost:3000/api"
}
Run Code Online (Sandbox Code Playgroud)

  • @AlbertoL.Bonfiglio 因为 Angular 应用程序本质上是客户端应用程序,并且 JavaScript 将用于传递数据和配置,所以其中不应该使用秘密配置;所有秘密配置定义都应位于 API 层后面,用户的浏览器或浏览器工具无法访问它。API 的基本 URI 之类的值可供公众访问,因为 API 应该拥有自己的凭据和基于用户登录的安全性(通过 https 的承载令牌)。 (5认同)
  • 拜托,你能帮我做对吗?与传统角度环境相比,它的风险如何?在ng build --prod之后的environments.prod.ts的全部内容在某个时候会在某个.js文件中。即使混淆,来自environments.prod.ts的数据也将以明文形式显示。作为所有.js文件,它将在最终用户计算机上可用。 (4认同)

Mat*_*ias 5

穷人的配置文件:

添加到 index.html 作为 body 标签的第一行:

<script lang="javascript" src="assets/config.js"></script>
Run Code Online (Sandbox Code Playgroud)

添加资产/config.js:

var config = {
    apiBaseUrl: "http://localhost:8080"
}
Run Code Online (Sandbox Code Playgroud)

添加 config.ts:

export const config: AppConfig = window['config']

export interface AppConfig {
    apiBaseUrl: string
}
Run Code Online (Sandbox Code Playgroud)

  • 说真的,+1 将解决方案归结为最基本的组件,并且仍然保持类型一致性。 (4认同)

Mik*_*ill 5

有相当多的文章建议您使用AppConfigService 诸如此之类的方法来获取 Angular 配置设置。

但我发现有时这不起作用。

拥有一个“ config.json ”文件更简单、更可靠,然后创建一个只读入它并返回一个值的类,例如我的配置文件如下所示:

{
  "appName": "Mike's app",
  "version": "1.23.4",
  "logging_URL" : "https://someWebsite.azurewebsites.net/logs"
}
Run Code Online (Sandbox Code Playgroud)

我将使用以下方法访问这些值:

import config from '../../assets/config.json';

@Injectable({
    providedIn: 'root'
})
export class AppConfigService {
    get appName() {
        return config.appName; 
    }
    get appVersion() {
        return config.version; 
    }
    get loggingUrl() {
        return config.logging_URL; 
    }
}
Run Code Online (Sandbox Code Playgroud)

(几个月后……)

在庆幸自己做出了一个更简单的 Angular 解决方案后,我意识到这有一个很大的缺点。如果您使用 AppConfigService,并且使用 CI/CD,那么您可以让构建过程更新 config .json 文件,并且 Angular 应用程序将使用这些设置。

对于我的版本,是的,它更简单,但是没有可以覆盖的配置设置文件。对于自动化构建过程,这可能是不可取的。

  • 我喜欢这种方法,因为它更容易集成,并且不会在网络选项卡中显示另一个 http 请求及其值。 (2认同)