关于typescript:定义全局常量

Define global constants

在角度1.x中,可以这样定义常量:

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

在Angular2中(使用typescript)的等效值是多少?

我只是不想在我的所有服务中反复地重复API基URL。


以下更改适用于Angular2最终版本:

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

然后在服务中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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);
    }
}


角团队提供的配置解决方案可以在这里找到。

以下是所有相关代码:

1)应用配置TS

1
2
3
4
5
6
7
8
9
10
11
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)应用模块TS

1
2
3
4
5
6
7
import { APP_CONFIG, AppConfig } from './app.config';

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

3)您的.service.ts

1
2
3
4
5
6
7
8
9
import { APP_CONFIG, IAppConfig } from './app.config';

@Injectable()
export class YourService {

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

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

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


在Angular2中,您有以下提供的定义,允许您设置不同类型的依赖项:

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

与角度1比较

角1中的app.service相当于角2中的useClass

角1中的app.factory相当于角2中的useFactory

app.constantapp.value已简化为useValue,约束较少。也就是说,再也没有config块了。

app.provider—角2中没有等价物。

示例

要设置根部喷油器:

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

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

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

provide是用于:

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

使用注入器,很容易获得值:

1
var endpoint = injectorValue.get(API_ENDPOINT);


在Angular4中,您可以使用环境类来保存所有全局变量。

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

例如

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

然后为您服务:

1
2
3
import { environment } from '../../environments/environment';
...
environment.apiUrl;


更新为角4+

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

例如

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

  • 埃多克斯1〔10〕
  • 江户十一〔11〕。
  • 埃多克斯1〔12〕

每个文件都可以保存相关的代码更改,例如:

  • 埃多克斯1〔10〕

    1
    2
    3
    4
    5
    6
    export const environment = {
         production: true,
         apiHost: 'https://api.somedomain.com/prod/v1/',
         CONSUMER_KEY: 'someReallyStupidTextWhichWeHumansCantRead',
         codes: [ 'AB', 'AC', 'XYZ' ],
    };

  • 江户十一〔11〕。

    1
    2
    3
    4
    5
    6
    export const environment = {
         production: false,
         apiHost: 'https://api.somedomain.com/qa/v1/',
         CONSUMER_KEY : 'someReallyStupidTextWhichWeHumansCantRead',
         codes: [ 'AB', 'AC', 'XYZ' ],
    };

  • 埃多克斯1〔12〕

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

应用中的用例

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

埃多克斯1〔17〕

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

号构建中的用例

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

1
2
3
4
5
6
7
8
"apps":[{
       "environments": {
           "dev":"environments/environment.ts",
           "prod":"environments/environment.prod.ts",
           "qa":"environments/environment.qa.ts",
           }
         }
       ]

如果您想为生产而构建,运行ng build --env=prod,它将从environment.prod.ts中读取配置,与对qadev的读取方式相同。

##旧的答案

我在我的供应商那里做了如下的事情:

1
2
3
4
5
6
7
8
9
10
11
12
13
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'
  }
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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)
  }
 }


虽然使用字符串常量作为apiendpoint的appsettings类的方法是可行的,但这并不理想,因为在单元测试时,我们无法将这个真正的apiendpoint替换为其他一些值。

我们需要能够将这个API端点注入到我们的服务中(考虑将服务注入到另一个服务中)。我们也不需要为此创建一个完整的类,我们只需要向我们的服务中注入一个字符串,作为我们的APIEndpoint。为了完成pixelbits的完美答案,以下是关于如何在角度2中完成的完整代码:

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

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

然后在服务中,我们将这个apiendpoint注入到服务构造函数中,Angular将根据上面的注册为我们提供它:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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...
}


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

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

我在这里跟踪了官方和更新的文档:

https://angular.io/docs/ts/latest/guide/dependency injection.html!#依赖注入令牌

似乎opaquetoken现在被弃用了,我们必须使用injectiontoken,所以这些是我的文件,运行起来像个符咒:

埃多克斯1〔5〕

1
2
3
4
5
export interface IAppConfig {

  STORE_KEY: string;

}

埃多克斯1〔6〕

1
2
3
4
5
6
7
8
9
10
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' );

江户十一〔七〕号

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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 {}

埃多克斯1〔8〕

1
2
3
4
5
6
7
  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@

  }

结果是干净的,控制台上没有警告。感谢约翰·帕帕最近在这个问题上的评论:

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

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


所有的解决方案似乎都很复杂。我在为这个例子寻找最简单的解决方案,我只想使用常量。常量很简单。有什么反对以下解决方案的吗?

应用常数ts

1
2
3
'use strict';

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

应用服务.ts

1
2
3
4
5
6
7
8
9
10
11
import * as AppConst from '../app.const';

@Injectable()
export class AppService {

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

}


只需使用typescript常量

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

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

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


如果您使用的是Webpack,我建议您可以为不同的环境设置常量。当您在每个环境中具有不同的常量值时,这一点尤其重要。

您的/config目录下可能有多个webpack文件(例如webpack.dev.js、webpack.prod.js等)。然后你会得到一个custom-typings.d.ts,你会把它们加在那里。下面是每个文件中要遵循的一般模式,以及组件中的示例用法。

webpack.env.js

1
2
3
4
5
6
7
8
9
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)
      }),

定制类型.d.ts

1
2
3
4
5
6
declare var API_URL: string;
declare var JWT_TOKEN_NAME: string;
interface GlobalEnvironment {
  API_URL: string;
  JWT_TOKEN_NAME: string;
}

组件

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

角度4的一种方法是在模块级定义一个常量:

1
2
3
4
5
6
7
8
9
10
11
12
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 {
}

那么,为您服务:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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);
    }
}


我有另一种定义全局常量的方法。因为如果我们在TS文件中定义,那么在生产模式中构建,就不容易找到常量来更改值。

1
2
3
4
5
6
7
8
9
10
11
12
13
export class SettingService  {

  constructor(private http: HttpClient) {

  }

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

在app文件夹中,我添加文件夹configs/setting.json

setting.json中的内容

1
2
3
{
   "baseUrl":"http://localhost:52555"
}

在应用程序模块中添加应用程序初始值设定项

1
2
3
4
5
6
   {
      provide: APP_INITIALIZER,
      useFactory: (setting: SettingService) => function() {return setting.getSetting()},
      deps: [SettingService],
      multi: true
    }

通过这种方式,我可以更容易地更改JSON文件中的值。我也用这种方式来处理持续的错误/警告消息。


使用在构建过程中生成的属性文件非常简单。这是Angular CLI使用的方法。为每个环境定义一个属性文件,并在生成期间使用一个命令来确定将哪个文件复制到应用程序。然后简单地导入要使用的属性文件。

https://github.com/angular/angular cli构建目标和环境文件


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

虽然它本身是一个提供者注册机制,但最好在相关的module.value($provide.value功能的上下文中理解它。官方文档清楚地说明了用例:

Register a value service with the $injector, such as a string, a number, an array, an object or a function. This is short for registering a service where its provider's $get property is a factory function that takes no arguments and returns the value service. That also means it is not possible to inject other services into a value service.

将其与module.constant($provide.constant)的文档进行比较,后者也清楚地说明了用例(emphasis mine):

Register a constant service with the $injector, such as a string, a number, an array, an object or a function. Like the value, it is not possible to inject other services into a constant.
But unlike value, a constant can be injected into a module configuration function (see angular.Module) and it cannot be overridden by an AngularJS decorator.

因此,AngularJS-constant函数并不能提供字段中术语的一般意义上的常量。

这就是说,对所提供对象的限制,以及通过$injector提供的早期可用性,都清楚地表明这个名称是通过类比来使用的。

如果您希望在AngularJS应用程序中有一个实际的常量,那么您可以像在任何JavaScript程序中那样"提供"一个常量。

1
export const π = 3.14159265;

在角2中,同样的技术也适用。

Angular2应用程序没有与AngularJS应用程序相同的配置阶段。此外,还没有服务装饰器机制(angularjs decorator),但考虑到它们之间的差异,这并不奇怪。

示例

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

有点武断,有点令人不快,因为$provide.constant正被用来指定一个偶然也是常量的对象。你还不如写

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

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

现在,对可测试性的争论,嘲弄常量,因为它实际上没有改变。

一个人不会嘲笑π。

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

但是在这种情况下,将它作为单个constant函数的URL的字符串文字表示提供是不起作用的。

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

这就是说,通过框架提供类似这样的东西会增加与该框架的耦合。它还混合了角度特定的逻辑和任何其他系统都可以工作的逻辑。

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

1
export const π = 3.14159265;

就像我会使用安古拉基一样。

事情越变…


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


您可以为全局变量生成一个类,然后像这样导出此类:

1
2
3
4
5
6
7
8
9
export class CONSTANT {
    public static message2 = [
        {"NAME_REQUIRED":"Name is required" }
    ]

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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(){}中使用,也可以在任何预先定义的方法中使用。