定义全局常数


258

在Angular 1.x中,您可以定义如下常量:

angular.module('mainApp.config', [])
    .constant('API_ENDPOINT', 'http://127.0.0.1:6666/api/')

Angular(带有TypeScript)的等效功能是什么?

我只是不想在所有服务中一遍又一遍地重复API基本URL。

Answers:


265

以下更改对我适用于Angular 2最终版本:

export class AppSettings {
   public static API_ENDPOINT='http://127.0.0.1:6666/api/';
}

然后在服务中:

import {Http} from 'angular2/http';
import {Message} from '../models/message';
import {Injectable} from 'angular2/core';
import {Observable} from 'rxjs/Observable';
import {AppSettings} from '../appSettings';
import 'rxjs/add/operator/map';

@Injectable()
export class MessageService {

    constructor(private http: Http) { }

    getMessages(): Observable<Message[]> {
        return this.http.get(AppSettings.API_ENDPOINT+'/messages')
            .map(response => response.json())
            .map((messages: Object[]) => {
                return messages.map(message => this.parseData(message));
            });
    }

    private parseData(data): Message {
        return new Message(data);
    }
}

我认为您的AppSettings课程应该是抽象的,而API_ENDPOINT成员应该是readonly
菲利普·乔奥斯菲

164

由角度小组本身提供的配置解决方案可在此处找到。

这是所有相关代码:

1)app.config.ts

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

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

export interface IAppConfig {
    apiEndpoint: string;
}

export const AppConfig: IAppConfig = {    
    apiEndpoint: "http://localhost:15422/api/"    
};

2)app.module.ts

import { APP_CONFIG, AppConfig } from './app.config';

@NgModule({
    providers: [
        { provide: APP_CONFIG, useValue: AppConfig }
    ]
})

3)your.service.ts

import { APP_CONFIG, IAppConfig } from './app.config';

@Injectable()
export class YourService {

    constructor(@Inject(APP_CONFIG) private config: IAppConfig) {
             // You can use config.apiEndpoint now
    }   
}

现在,您可以将配置注入到任何地方,而无需使用字符串名称,也无需使用接口进行静态检查。

您当然可以进一步分离接口和常量,以便在生产和开发中提供不同的值,例如


3
仅当我没有在服务的构造函数中指定类型时,它才起作用。因此,当我执行构造函数时,它可以工作(@Inject(APP_CONFIG)private config){}这里提到了这一点: blog.thoughtram.io/angular/2016/05/23/… 但不是为什么。
Mukus

我想您可能错过了一些import或export关键字之类的东西,因为我在界面上使用了它,正如您所说的,让它显式地静态键入非常重要。也许您需要在此处提供确切的例外。
Ilya Chernomordik

46
这些解决方案都没有,即使有角度的团队推荐的方法看起来也很优雅。为什么在Angular 2中尝试创建常数是一个麻烦的过程?无法看到Angular1是如何做到无缝的?为什么一团糟?
KhoPhi

31
对于其他任何人点击这个答案的OpaqueToken在角V4为“过时”的InjectionToken - blog.thoughtram.io/angular/2016/05/23/...
mtpultz

3
将第1步中的代码放入environment.tsenvironment.prod.ts在每个环境中具有不同的常量是否有意义?@IlyaChernomordik在他的答案的最后一段中开始提到这一点。
罗伯特·伯恩斯坦

64

在Angular2中,您具有以下提供的定义,该定义使您可以设置不同类型的依赖项:

provide(token: any, {useClass, useValue, useExisting, useFactory, deps, multi}

与Angular 1比较

app.serviceAngular1中的等效于Angular2中的等效useClass

app.factoryAngular1中的等效于Angular2中的等效useFactory

app.constantapp.value已简化为useValue较少约束。即没有config障碍了。

app.provider -Angular 2中没有等效功能。

例子

要设置根注入器:

bootstrap(AppComponent,[provide(API_ENDPOINT, { useValue='http://127.0.0.1:6666/api/' })]);

或使用组件的进样器进行设置:

providers: [provide(API_ENDPOINT, { useValue: 'http://127.0.0.1:6666/api/'})]

provide 是以下方面的简称:

var injectorValue = Injector.resolveAndCreate([
  new Provider(API_ENDPOINT, { useValue: 'http://127.0.0.1:6666/api/'})
]);

使用进样器,很容易获得价值:

var endpoint = injectorValue.get(API_ENDPOINT);

2
我实际上希望将设置保存在外部文件中,例如:settings.ts该文件的外观如何?
AndreFeijo

您是否考虑过诸如NodeJS之类的服务器端JavaScript?
pixelbits

5
抱歉,我不知道如何将其注入服务?使用外部文件时,是否需要导出?
AndreFeijo

我会将其纳入您的构建配置过程。即根据您的环境,将不同的文件编译/打包在一起,然后进行部署。您可以使用带有适当模块的NodeJS进行所有这些操作。
pixelbits

1
不幸的是,NodeJS不是一个选择。
AndreFeijo

59

在Angular 4中,您可以使用环境类保留所有全局变量。

默认情况下,您具有environment.ts和environment.prod.ts。

例如

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

然后为您服务:

import { environment } from '../../environments/environment';
...
environment.apiUrl;

如果您尝试访问const服务内部,则可能必须在应用程序模块的providers数组中“提供”该服务{ provide: 'ConstName', useValue: ConstName }。没有这个我遇到了运行时错误。
daleyjem '18年

@daleyjem那是因为您试图注入它。这种方法不使用喷射器
Aluan Haddad '18

创建这样的常量是最简单的。我猜想,失去DI从而失去可测试性/模拟价值的反论点被过度炒作了。在典型的应用程序中,我们使用了很多非DI组件,例如(RxJS),而不会影响可测试性。
阿米特什

54

已针对Angular 4+更新

现在,如果您的项目是通过angular-cli生成的,则我们可以简单地使用环境文件中所提供的默认值。

例如

在您的环境文件夹中创建以下文件

  • environment.prod.ts
  • environment.qa.ts
  • environment.dev.ts

每个文件可以包含相关的代码更改,例如:

  • environment.prod.ts

    export const environment = {
         production: true,
         apiHost: 'https://api.somedomain.com/prod/v1/',
         CONSUMER_KEY: 'someReallyStupidTextWhichWeHumansCantRead', 
         codes: [ 'AB', 'AC', 'XYZ' ],
    };
  • environment.qa.ts

    export const environment = {
         production: false,
         apiHost: 'https://api.somedomain.com/qa/v1/',
         CONSUMER_KEY : 'someReallyStupidTextWhichWeHumansCantRead', 
         codes: [ 'AB', 'AC', 'XYZ' ],
    };
  • environment.dev.ts

    export const environment = {
         production: false,
         apiHost: 'https://api.somedomain.com/dev/v1/',
         CONSUMER_KEY : 'someReallyStupidTextWhichWeHumansCantRead', 
         codes: [ 'AB', 'AC', 'XYZ' ],
    };

应用中的用例

您可以将环境导入任何文件,例如服务 clientUtilServices.ts

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

getHostURL(): string {
    return environment.apiHost;
  }

构建用例

打开您的angular cli文件.angular-cli.json并在其中"apps": [{...}]添加以下代码

 "apps":[{
        "environments": {
            "dev": "environments/environment.ts",
            "prod": "environments/environment.prod.ts",
            "qa": "environments/environment.qa.ts",
           }
         }
       ]

如果您要进行量产,请运行, ng build --env=prod然后从中读取配置 environment.prod.ts,方法与qadev

##较旧的答案

我在提供程序中一直在执行以下操作:

import {Injectable} from '@angular/core';

@Injectable()
export class ConstantService {

API_ENDPOINT :String;
CONSUMER_KEY : String;

constructor() {
    this.API_ENDPOINT = 'https://api.somedomain.com/v1/';
    this.CONSUMER_KEY = 'someReallyStupidTextWhichWeHumansCantRead'
  }
}

然后我可以在任何地方访问所有Constant数据

import {Injectable} from '@angular/core';
import {Http} from '@angular/http';
import 'rxjs/add/operator/map';

import {ConstantService} from  './constant-service'; //This is my Constant Service


@Injectable()
export class ImagesService {
    constructor(public http: Http, public ConstantService: ConstantService) {
    console.log('Hello ImagesService Provider');

    }

callSomeService() {

    console.log("API_ENDPOINT: ",this.ConstantService.API_ENDPOINT);
    console.log("CONSUMER_KEY: ",this.ConstantService.CONSUMER_KEY);
    var url = this.ConstantService.API_ENDPOINT;
    return this.http.get(url)
  }
 }

6
这不像常量那样工作。常量的值始终相同。就您而言,您的API_ENDPOINT价值可以在任何时间点被覆盖。如果this.ConstantService.API_ENDPOINT = 'blah blah'从中导入了所谓的“常量”之后的任何时候在类中声明,则constant-serviceAPI_ENDPOINT的新值将为'blah blah'。您的解决方案仅显示了如何使用服务而不是常量来访问变量。
Devner '17

1
@Devner只是将其readonly API_ENDPOINT :String;
设为

@Anjum如何角度选择环境文件。启动应用程序时是否需要传递环境名称?
notionquest '18

@notionquest是的,您可以通过它,例如ng build --env=prod
Anjum ....

31

尽管使用带有ApiEndpoint作为字符串常量的AppSettings类的方法行得通,但这种方法并不理想,因为在单元测试时,我们无法将实际的ApiEndpoint交换为其他值。

我们需要能够将此api端点注入到我们的服务中(考虑将服务注入到另一个服务中)。我们也不需要为此创建一个完整的类,我们要做的就是向我们的服务ApiEndpoint中注入一个字符串。为了通过pixelbits完成出色的回答,以下是有关如何在Angular 2中完成操作的完整代码:

首先,当我们在应用程序中要求它时,我们需要告诉Angular如何提供我们的ApiEndpoint实例(将其视为注册依赖项):

bootstrap(AppComponent, [
        HTTP_PROVIDERS,
        provide('ApiEndpoint', {useValue: 'http://127.0.0.1:6666/api/'})
]);         


然后在服务,我们注入这个ApiEndpoint到服务构造和角度将根据我们上面的注册提供它为我们:

import {Http} from 'angular2/http';
import {Message} from '../models/message';
import {Injectable, Inject} from 'angular2/core';  // * We import Inject here
import {Observable} from 'rxjs/Observable';
import {AppSettings} from '../appSettings';
import 'rxjs/add/operator/map';

@Injectable()
export class MessageService {

    constructor(private http: Http, 
                @Inject('ApiEndpoint') private apiEndpoint: string) { }

    getMessages(): Observable<Message[]> {
        return this.http.get(`${this.apiEndpoint}/messages`)
            .map(response => response.json())
            .map((messages: Object[]) => {
                return messages.map(message => this.parseData(message));
            });
    } 
    // the rest of the code...
}

1
现在,Angular团队在他们的教程中提供了一种“官方”推荐方法。我在下面添加了一个答案:(stackoverflow.com/a/40287063/1671558
Ilya Chernomordik

1
此代码不再准确,实现此代码将导致在AppComponent上找不到ApiEndpoint。
WilliamX

好吧,我并不孤单。你知道这是什么版本的吗?是否有一种不需要在全局对象上定义值然后提供它们的替代方法?
詹斯·鲍德

29

这是我最近在这种情况下的经验:

  • @角度/ cli:1.0.0
  • 节点:6.10.2
  • @角/核心:4.0.0

我在这里关注了官方和更新的文档:

https://angular.io/docs/ts/latest/guide/dependency-injection.html#!#dependency-injection-tokens

似乎OpaqueToken现在已被弃用,我们必须使用InjectionToken,所以这些文件运行时就像一个超级按钮

app-config.interface.ts

export interface IAppConfig {

  STORE_KEY: string;

}

app-config.constants.ts

import { InjectionToken } from "@angular/core";
import { IAppConfig } from "./app-config.interface";

export const APP_DI_CONFIG: IAppConfig = {

  STORE_KEY: 'l@_list@'

};

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

app.module.ts

import { APP_CONFIG, APP_DI_CONFIG } from "./app-config/app-config.constants";

@NgModule( {
  declarations: [ ... ],
  imports: [ ... ],
  providers: [
    ...,
    {
      provide: APP_CONFIG,
      useValue: APP_DI_CONFIG
    }
  ],
  bootstrap: [ ... ]
} )
export class AppModule {}

my-service.service.ts

  constructor( ...,
               @Inject( APP_CONFIG ) private config: IAppConfig) {

    console.log("This is the App's Key: ", this.config.STORE_KEY);
    //> This is the App's Key:  l@_list@

  }

结果是干净的,并且在控制台上没有警告,感谢John Papa在此问题上的最新评论:

https://github.com/angular/angular-cli/issues/2034

关键是在另一个文件中实现接口。


另请参见stackoverflow.com/a/43193574/3092596-基本相同,但是创建可注入模块而不是提供程序
goredwards 2015年

19

所有解决方案似乎都很复杂。我正在寻找这种情况的最简单解决方案,而我只想使用常量。常数很简单。有什么反对下面的解决方案吗?

应用程序常量

'use strict';

export const dist = '../path/to/dist/';

app.service.ts

import * as AppConst from '../app.const'; 

@Injectable()
export class AppService {

    constructor (
    ) {
        console.log('dist path', AppConst.dist );
    }

}

2
好吧,您正在使用服务范围之外的变量,因此您也可以只使用窗口全局变量。我们试图做的是将常量放入Angular4依赖注入系统中,以便我们可以保持范围干净,可存或可模拟。
乔尔·埃尔南德斯

11

只需使用Typescript常量

export var API_ENDPOINT = 'http://127.0.0.1:6666/api/';

您可以在依赖注入器中使用它

bootstrap(AppComponent, [provide(API_ENDPOINT, {useValue: 'http://127.0.0.1:6666/api/'}), ...]);

1
为什么要注射呢?我认为不需要...您可以在导入后立即使用它。@SnareChops
萨斯克斯

@Sasxa我同意,尽管它对于单元测试等可能很好。只是试图提供一个完整的答案。
SnareChops

1
@Andreas您可以使用constyest
SnareChops

请提供有关此工作的概述。我已经看到了很多使用bootstrap方法提供服务的示例,但是还没有找到一个具有足够工作示例的示例。在最新版本的angular中可能发生了某些变化。
詹斯·鲍德

4

如果您使用的是Webpack,我建议您为不同的环境设置常量。当您在每个环境中具有不同的常量值时,这特别有价值。

您的/config目录下可能会有多个webpack文件(例如,webpack.dev.js,webpack.prod.js等)。那你会有一个custom-typings.d.ts将其添加到那里。这是每个文件中遵循的一般模式以及组件中的示例用法。

webpack。{env} .js

const API_URL = process.env.API_URL = 'http://localhost:3000/';
const JWT_TOKEN_NAME = "id_token";
...
    plugins: [
      // NOTE: when adding more properties, make sure you include them in custom-typings.d.ts
      new DefinePlugin({
        'API_URL': JSON.stringify(API_URL),
        'JWT_TOKEN_NAME': JSON.stringify(JWT_TOKEN_NAME)
      }),

自定义类型

declare var API_URL: string;
declare var JWT_TOKEN_NAME: string;
interface GlobalEnvironment {
  API_URL: string;
  JWT_TOKEN_NAME: string;
}

零件

export class HomeComponent implements OnInit {
  api_url:string = API_URL;
  authToken: string = "Bearer " + localStorage.getItem(JWT_TOKEN_NAME)});
}


3

Angular4的一种方法是在模块级别定义一个常量:

const api_endpoint = 'http://127.0.0.1:6666/api/';

@NgModule({
  declarations: [AppComponent],
  bootstrap: [AppComponent],
  providers: [
    MessageService,
    {provide: 'API_ENDPOINT', useValue: api_endpoint}
  ]
})
export class AppModule {
}

然后,为您服务:

import {Injectable, Inject} from '@angular/core';

@Injectable()
export class MessageService {

    constructor(private http: Http, 
      @Inject('API_ENDPOINT') private api_endpoint: string) { }

    getMessages(): Observable<Message[]> {
        return this.http.get(this.api_endpoint+'/messages')
            .map(response => response.json())
            .map((messages: Object[]) => {
                return messages.map(message => this.parseData(message));
            });
    }

    private parseData(data): Message {
        return new Message(data);
    }
}

3

我还有另一种定义全局常量的方法。因为如果我们在ts文件中定义,如果在生产模式下构建,则不容易找到要更改值的常量。

export class SettingService  {

  constructor(private http: HttpClient) {

  }

  public getJSON(file): Observable<any> {
      return this.http.get("./assets/configs/" + file + ".json");
  }
  public getSetting(){
      // use setting here
  }
}

在应用程序文件夹中,我添加文件夹configs / setting.json

setting.json中的内容

{
    "baseUrl": "http://localhost:52555"
}

在应用模块中添加APP_INITIALIZER

   {
      provide: APP_INITIALIZER,
      useFactory: (setting: SettingService) => function() {return setting.getSetting()},
      deps: [SettingService],
      multi: true
    }

通过这种方式,我可以更轻松地更改json文件中的值。我也使用这种方式来不断出现错误/警告消息。


0

AngularJS的 module.constant在标准意义上没有定义常量。

尽管它本身是提供者注册机制,但最好在相关module.value$provide.value)函数的上下文中对其进行理解。官方文档明确说明了用例:

向$ injector注册一个值服务,例如字符串,数字,数组,对象或函数。这是注册服务的简称,该服务的提供者的$ get属性是一个不带任何参数并返回值service的工厂函数。这也意味着不可能将其他服务注入到价值服务中。

将此与module.constant$provide.constant)的文档进行比较,该文档也清楚说明了用例(强调我的用例):

向$ injector注册一个常量服务,例如字符串,数字,数组,对象或函数。像该值一样,不可能将其他服务注入一个常量。但是与值不同,可以将常量注入模块配置函数(请参见angular.Module),并且AngularJS装饰器不能覆盖该常量

因此,AngularJS constant函数无法提供该术语在本领域中通常理解的常数。

也就是说,对提供的对象施加的限制以及通过$ injector对其的早期可用性,清楚地表明该名称已被类推使用。

如果您想要AngularJS应用程序中的实际常量,则可以像在任何JavaScript程序中一样“提供”一种

export const π = 3.14159265;

在Angular 2中,可以应用相同的技术。

Angular 2应用程序没有与AngularJS应用程序相同的配置阶段。此外,没有服务装饰器机制(AngularJS Decorator),但是鉴于它们彼此之间的差异,这并不奇怪。

的例子

angular
  .module('mainApp.config', [])
  .constant('API_ENDPOINT', 'http://127.0.0.1:6666/api/');

是隐约任意的,并且稍微偏离推杆因为$provide.constant被用于指定一个对象,它是顺便也为常数。您可能还写了

export const apiEndpoint = 'http://127.0.0.1:6666/api/';

因为任何一个都可以改变。

现在,模拟常量的可测试性参数已减少,因为它实际上不会发生变化。

一个不嘲笑π。

当然,您的应用程序特定的语义可能是您的端点可能更改,或者您的API可能具有非透明的故障转移机制,因此在某些情况下更改API端点是合理的。

但是在那种情况下,将其提供为该constant功能的单个URL的字符串文字表示形式将无法正常工作。

一个更好的论点,并且可能与AngularJS $provide.constant函数存在的原因更加一致,是当引入AngularJS时,JavaScript没有标准的模块概念。在这种情况下,将使用全局变量来共享可变或不变的价值,并且使用全局变量是有问题的。

也就是说,通过框架提供类似的内容会增加与该框架的耦合。它还将特定于Angular的逻辑与可以在任何其他系统中运行的逻辑混合在一起。

这并不是说这是一种错误或有害的方法,但就我个人而言,如果我想在Angular 2应用程序中使用常量,我会写

export const π = 3.14159265;

就像我使用AngularJS一样。

更多的事情改变了...


0

在Angular 2中创建应用程序级常量的最佳方法是使用environment.ts文件。声明这样的常量的好处是,您可以根据环境来改变它们,因为每个环境可以有不同的环境文件。


如果您打算先构建应用程序然后将其部署到多个环境,则此方法将无效。
詹斯·鲍德

-1

您可以为全局变量创建一个类,然后像这样导出该类:

export class CONSTANT {
    public static message2 = [
        { "NAME_REQUIRED": "Name is required" }
    ]

    public static message = {
        "NAME_REQUIRED": "Name is required",
    }
}

创建并导出您的CONSTANT类后,应在要使用的类中导入该类,如下所示:

import { Component, OnInit                       } from '@angular/core';
import { CONSTANT                                } from '../../constants/dash-constant';


@Component({
  selector   : 'team-component',
  templateUrl: `../app/modules/dashboard/dashComponents/teamComponents/team.component.html`,
})

export class TeamComponent implements OnInit {
  constructor() {
    console.log(CONSTANT.message2[0].NAME_REQUIRED);
    console.log(CONSTANT.message.NAME_REQUIRED);
  }

  ngOnInit() {
    console.log("oninit");
    console.log(CONSTANT.message2[0].NAME_REQUIRED);
    console.log(CONSTANT.message.NAME_REQUIRED);
  }
}

您可以在使用constructorngOnInit(){},或以任何预先定义的方法。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.