App.settings - Angular方式?

时间:2017-04-03 19:45:56

标签: javascript angular

我想在我的App中添加App Settings部分,其中包含一些consts和预定义的值。

我已经阅读了使用OpaqueToken的{​​{3}}但是在Angular中已弃用。这个this answer解释了这些差异,但它没有提供完整的示例,我的尝试也没有成功。

这是我尝试过的(我不知道它是否正确):

//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');

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

//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 {


}

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

问题:

我如何消费&#34; app.settings&#34;重视Angular方式?

article

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

10 个答案:

答案 0 :(得分:121)

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

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

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

environment.ts

export const environment = {
  production: false,
  apiEndpoint: 'http://localhost:8000/api/v1'
};

environment.prod.ts

export const environment = {
  production: true,
  apiEndpoint: '__your_production_server__'
};

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

<强> api.service.ts

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

@Injectable()
export class ApiService {     

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

// ...
}

详细了解Github (Angular CLI version 6)official Angular guide (version 7)上的应用环境。

答案 1 :(得分:40)

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

注入令牌示例

<强> /app/app-config.module.ts

InjectionToken

<强> /app/app.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 { }

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

<强> /app/core/auth.service.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 { }

然后,您还可以使用导出的AppConfig键入check config。

答案 2 :(得分:12)

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

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

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

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

要实现运行时配置:

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

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

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

{
  "apiBaseUrl": "https://development.local/apiUrl"
}

2。创建AppConfigService

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

@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;
  }
}

3。使用APP_INITIALISER

加载配置

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

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 { }

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

@Component({
  selector: 'app-test',
  templateUrl: './test.component.html',
  styleUrls: ['./test.component.scss']
})
export class TestComponent implements OnInit {

  apiBaseUrl: string;

  constructor(private appConfigService: AppConfigService) {}

  ngOnInit(): void {
    this.apiBaseUrl = this.appConfigService.apiBaseUrl;
  }

}

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

答案 3 :(得分:7)

这是我的解决方案,从.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);
    }
}

和config.json

{
    "apiUrl": "http://localhost:3000/api"
}

答案 4 :(得分:5)

可怜的人的配置文件:

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

sns.scatterplot(x='X [um]', y='Y [um]', hue='label', size='size', data=data)

添加资产/config.js:

<script lang="javascript" src="assets/config.js"></script>

添加config.ts:

var config = {
    apiBaseUrl: "http://localhost:8080"
}

答案 5 :(得分:5)

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

我已经看到其他解决方案,它们使用fetch来读取config.json文件,并在引导根模块之前使用platformBrowserDynamic()的参数中的注入令牌来提供该文件。但是fetch并非在我所定位的移动设备上的所有浏览器中都支持,特别是在WebView浏览器中。

以下是适用于PWA和移动设备(WebView)的解决方案。注意:到目前为止,我仅在Android上进行过测试;在家工作意味着我无法使用Mac进行构建。

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();

此代码:

  1. 启动对config.json文件的异步请求。
  2. 请求完成后,将JSON解析为Javascript对象
  3. 在引导前使用APP_CONFIG注入令牌提供值。
  4. 最后引导根模块。
然后可以将

APP_CONFIG注入到app-module.ts中的任何其他提供程序中,并将对其进行定义。例如,我可以使用以下初始化来自FIREBASE_OPTIONS的{​​{1}}注入令牌:

@angular/fire

对于一个非常普遍的要求,我发现整件事很难做到(而且很棘手)。希望在不久的将来会有更好的方法,例如对异步提供程序工厂的支持。

其他代码的完整性...

{ provide: FIREBASE_OPTIONS, useFactory: (config: IConfig) => config.firebaseConfig, deps: [APP_CONFIG] } 中:

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'); 中,我定义了JSON配置文件的接口:

app/lib/config/config.ts

配置存储在export interface IConfig { name: string; version: string; instance: string; firebaseConfig: { apiKey: string; // etc } } 中:

assets/config/config.json

注意:我使用Azure DevOps任务来插入Build.BuildNumber并在部署时将其他设置替换为不同的部署环境。

答案 6 :(得分:1)

这是我的两个解决方案

1。存储在json文件中

只需制作一个json文件,然后通过$http.get()方法进入您的组件。如果我需要这个很低的东西,那就好又快。

2。通过使用数据服务进行存储

如果要存储并在所有组件中使用或有大量使用,那么最好使用数据服务。像这样:

  1. 只需在src/app文件夹中创建静态文件夹。

  2. 在静态文件夹中创建一个名为fuels.ts的文件。您也可以在这里存储其他静态文件。让我们这样定义您的数据。假设您拥有加油数据。

__

export const Fuels {

   Fuel: [
    { "id": 1, "type": "A" },
    { "id": 2, "type": "B" },
    { "id": 3, "type": "C" },
    { "id": 4, "type": "D" },
   ];
   }
  1. 创建文件名static.services.ts

__

import { Injectable } from "@angular/core";
import { Fuels } from "./static/fuels";

@Injectable()
export class StaticService {

  constructor() { }

  getFuelData(): Fuels[] {
    return Fuels;
  }
 }`
  1. 现在您可以将其用于每个模块

只需要像这样导入app.module.ts文件并更改提供者

import { StaticService } from './static.services';

providers: [StaticService]

现在在任何模块中都将其用作StaticService

仅此而已。

答案 7 :(得分:0)

我发现来自Microsoft Dev博客的Angular How-to: Editable Config Files是最好的解决方案。您可以配置开发构建设置或生产构建设置。

答案 8 :(得分:0)

几年前,我们遇到了这个问题,然后我加入了一个解决方案,该解决方案使用本地存储来存储用户和环境信息。精确的角度为1.0天。我们以前是在运行时动态创建一个js文件,然后将生成的api url放入一个全局变量中。如今,我们受OOP驱动更多,并且不再使用本地存储。

我为确定环境和api URL创建了一个更好的解决方案。

这有何不同?

除非加载config.json文件,否则不会加载该应用程序。它使用工厂功能来创建更高级别的SOC。我可以将其封装到服务中,但是当文件的不同部分之间唯一的相似之处是它们在文件中同时存在时,我从来没有看到任何原因。如果具有工厂功能,则可以将功能直接传递到模块中(如果它可以接受功能)。最后,当可以使用工厂功能时,我可以更轻松地设置InjectionTokens。

缺点?

如果您要配置的模块不允许将工厂函数传递给forRoot()或forChild(),并且您没有使用此设置,那么您将无法使用此设置(以及大多数其他答案)使用工厂功能配置软件包的其他方法。

说明

  1. 使用获取来检索json文件,我将该对象存储在window中并引发自定义事件。 -请记住安装whatwg-fetch并将其添加到您的polyfills.ts中以实现IE兼容性
  2. 让事件监听器监听自定义事件。
  3. 事件侦听器接收事件,从窗口检索对象以传递给可观察对象,并清除窗口中存储的内容。
  4. Bootstrap Angular

-这是我的解决方案开始与众不同的地方-

  1. 创建一个文件,导出一个接口,该接口的结构表示您的config.json -确实有助于类型一致性,下一部分代码需要一个类型,并且不要指定{}any当您知道可以指定更具体的内容
  2. 创建BehaviorSubject,您将在步骤3中将解析的json文件传递到其中。
  3. 使用工厂功能来引用配置的不同部分以维护SOC
  4. 为需要工厂函数结果的提供者创建InjectionTokens

-和/或-

  1. 将工厂函数直接传递到能够通过其forRoot()或forChild()方法接受函数的模块中。

-main.ts

在创建事件侦听器之前,我未检查window [“ environment”]的存在,以允许通过main.ts中的代码执行之前通过其他某种方式填充window [“ environment”]的解决方案。 / p>

import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
import { configurationSubject } from './app/utils/environment-resolver';

var configurationLoadedEvent = document.createEvent('Event');
configurationLoadedEvent.initEvent('config-set', true, true);
fetch("../../assets/config.json")
.then(result => { return result.json(); })
.then(data => {
  window["environment"] = data;
  document.dispatchEvent(configurationLoadedEvent);
}, error => window.location.reload());

/*
  angular-cli only loads the first thing it finds it needs a dependency under /app in main.ts when under local scope. 
  Make AppModule the first dependency it needs and the rest are done for ya. Event listeners are 
  ran at a higher level of scope bypassing the behavior of not loading AppModule when the 
  configurationSubject is referenced before calling platformBrowserDynamic().bootstrapModule(AppModule)

  example: this will not work because configurationSubject is the first dependency the compiler realizes that lives under 
  app and will ONLY load that dependency, making AppModule an empty object.

  if(window["environment"])
  {
    if (window["environment"].production) {
      enableProdMode();
    }
    configurationSubject.next(window["environment"]);
    platformBrowserDynamic().bootstrapModule(AppModule)
    .catch(err => console.log(err));
  }
*/
if(!window["environment"]) {
  document.addEventListener('config-set', function(e){
    if (window["environment"].production) {
      enableProdMode();
    }
    configurationSubject.next(window["environment"]);
    window["environment"] = undefined;
    platformBrowserDynamic().bootstrapModule(AppModule)
    .catch(err => console.log(err));
  });
}

--- environment-resolvers.ts

我使用window [“ environment”]为BehaviorSubject分配了一个值,以实现冗余。您可以设计一个解决方案,其中在您的任何Angular应用程序代码(包括main.ts中的代码)运行时,已经预先加载了配置并且已经填充window [“ environment”]。

import { BehaviorSubject } from "rxjs";
import { IConfig } from "../config.interface";

const config = <IConfig>Object.assign({}, window["environment"]);
export const configurationSubject = new BehaviorSubject<IConfig>(config);
export function resolveEnvironment() {
  const env = configurationSubject.getValue().environment;
  let resolvedEnvironment = "";
  switch (env) {
 // case statements for determining whether this is dev, test, stage, or prod
  }
  return resolvedEnvironment;
}

export function resolveNgxLoggerConfig() {
  return configurationSubject.getValue().logging;
}

-app.module.ts-精简以便于理解

有趣的事实!较旧的NGXLogger版本要求您将一个对象传递到LoggerModule.forRoot()。实际上,LoggerModule仍然可以! NGXLogger请公开LoggerConfig,您可以重写它,以便使用工厂功能进行设置。

import { resolveEnvironment, resolveNgxLoggerConfig, resolveSomethingElse } from './environment-resolvers';
import { LoggerConfig } from 'ngx-logger';
@NgModule({
    modules: [
        SomeModule.forRoot(resolveSomethingElse)
    ],
    providers:[
        {
            provide: ENVIRONMENT,
            useFactory: resolveEnvironment
        },
        { 
            provide: LoggerConfig,
            useFactory: resolveNgxLoggerConfig
        }
    ]
})
export class AppModule

附录

我如何解决API网址的创建?

我希望能够通过注释来理解每个URL的功能,并希望进行类型检查,因为与JavaScript(IMO)相比,这是TypeScript的最大优势。我还想为其他开发人员创造一种体验,以添加尽可能无缝的新端点和api。

我创建了一个接受环境的类(dev,test,stage,prod,“”等),并将此值传递给一系列类[1-N],其工作是为该类创建基本URL每个API集合。每个ApiCollection负责为每个API集合创建基本URL。可能是我们自己的API,供应商的API,甚至是外部链接。该类会将创建的基本url传递到它包含的每个后续api中。阅读下面的代码以查看示例。设置完成后,对于另一个开发人员而言,将另一个端点添加到Api类非常简单,而无需进行任何其他操作。

TLDR;基本的OOP原理和用于内存优化的惰性获取器

@Injectable({
    providedIn: 'root'
})
export class ApiConfig {
    public apis: Apis;

    constructor(@Inject(ENVIRONMENT) private environment: string) {
        this.apis = new Apis(environment);
    }
}

export class Apis {
    readonly microservices: MicroserviceApiCollection;

    constructor(environment: string) {
        this.microservices = new MicroserviceApiCollection(environment);
    }
}

export abstract class ApiCollection {
  protected domain: any;

  constructor(environment: string) {
      const domain = this.resolveDomain(environment);
      Object.defineProperty(ApiCollection.prototype, 'domain', {
          get() {
              Object.defineProperty(this, 'domain', { value: domain });
              return this.domain;
          },
          configurable: true
      });
  }
}

export class MicroserviceApiCollection extends ApiCollection {
  public member: MemberApi;

  constructor(environment) {
      super(environment);
      this.member = new MemberApi(this.domain);
  }

  resolveDomain(environment: string): string {
      return `https://subdomain${environment}.actualdomain.com/`;
  }
}

export class Api {
  readonly base: any;

  constructor(baseUrl: string) {
      Object.defineProperty(this, 'base', {
          get() {
              Object.defineProperty(this, 'base',
              { value: baseUrl, configurable: true});
              return this.base;
          },
          enumerable: false,
          configurable: true
      });
  }

  attachProperty(name: string, value: any, enumerable?: boolean) {
      Object.defineProperty(this, name,
      { value, writable: false, configurable: true, enumerable: enumerable || true });
  }
}

export class MemberApi extends Api {

  /**
  * This comment will show up when referencing this.apiConfig.apis.microservices.member.memberInfo
  */
  get MemberInfo() {
    this.attachProperty("MemberInfo", `${this.base}basic-info`);
    return this.MemberInfo;
  }

  constructor(baseUrl: string) {
    super(baseUrl + "member/api/");
  }
}

答案 9 :(得分:0)

很多文章都建议您使用 AppConfigService such as this one 获取 Angular 配置设置。

但我发现有时这行不通。

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

{
  "appName": "Mike's app",
  "version": "1.23.4",
  "logging_URL" : "https://someWebsite.azurewebsites.net/logs"
}

我会使用这个访问值:

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; 
    }
}