Определить глобальные константы

258

В Angular 1.x вы можете определить константы следующим образом:

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

Что было бы эквивалентно в Angular (с TypeScript)?

Я просто не хочу повторять базовый URL API снова и снова во всех моих сервисах.

AndreFeijo
источник

Ответы:

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);
    }
}
AndreFeijo
источник
Я думаю, что ваш 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) частная конфигурация) {} Здесь есть упоминание об этом: blog.thoughtram.io/angular/2016/05/23/… но не почему.
Мукус
Я предполагаю, что вы пропустили какое-то ключевое слово import или export или что-то в этом роде, поскольку я использую его с интерфейсом, и, как вы говорите, очень важно иметь его в явном статическом типе. Может быть, вам нужно предоставить точное исключение здесь.
Илья Черномордик
46
Ни одно из этих решений, даже рекомендуемый угловым коллективом подход, не выглядит элегантно. Почему в Angular 2 пытаются создавать константы громоздким процессом? Не можете ли вы увидеть, как без проблем Angular1 сделал это? Почему весь беспорядок?
Хофи
31
Для всех, кто ответит на этот ответ, OpaqueToken в Angular v4 «не рекомендуется» для InjectionToken - blog.thoughtram.io/angular/2016/05/23/…
mtpultz
3
Имеет ли смысл поместить код из шага 1 environment.tsи environment.prod.tsполучить разные константы для каждой среды? @IlyaChernomordik начал упоминать об этом в последнем абзаце своего ответа.
Роберт Бернштейн
64

В Angular2 у вас есть следующее определение определения, которое позволяет вам устанавливать различные виды зависимостей:

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

По сравнению с угловой 1

app.serviceв Angular1 эквивалентно useClassв Angular2.

app.factoryв Angular1 эквивалентно useFactoryв Angular2.

app.constantи app.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);
pixelbits
источник
2
Я действительно хотел бы, чтобы мои настройки были во внешнем файле, например: settings.ts Как бы выглядел этот файл?
AndreFeijo
Рассматривали ли вы серверный JavaScript, такой как NodeJS?
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внутренней части сервиса, возможно , придется «обеспечить» его в массиве поставщиков своего приложения модуля: { provide: 'ConstName', useValue: ConstName }. Я получил ошибку во время выполнения без этого.
daleyjem
@daleyjem это потому что ты пытался это сделать. Этот подход не использует инжектор
Алуан Хаддад
Создать такую ​​константу проще всего. Я предполагаю, что встречный аргумент о потере DI и, таким образом, о потере testability / mockValue, иногда переоценивается. В типичном приложении мы используем так много компонентов, не относящихся к DI, как (RxJS), не мешая тестированию.
Амитеш
54

Обновлено для Angular 4+

Теперь мы можем просто использовать файл сред, который angular предоставляет по умолчанию, если ваш проект генерируется с помощью 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;
  }

Вариант использования в сборке

Откройте свой угловой файл 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, так же, как вы можете сделать это для qaилиdev

## Старый ответ

Я делал что-то вроде ниже, в моем провайдере:

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

Тогда у меня есть доступ ко всем постоянным данным в любом месте

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)
  }
 }
Anjum ....
источник
6
Это не работает как константа. Значение константы всегда одинаково. В вашем случае ваше API_ENDPOINTзначение может быть переписано в любой момент времени. Если this.ConstantService.API_ENDPOINT = 'blah blah'объявлено в классе в любое время после того, как ваша так называемая «константа» импортирована из constant-service, новое значение API_ENDPOINT будет 'blah blah'. Ваше решение просто показывает, как получить доступ к переменной с помощью службы, а не с помощью константы.
Девнер
1
@Devner просто сделать их только для чтенияreadonly API_ENDPOINT :String;
Флавиен
@Anjum Как угловые выбирает файлы env. Нужно ли передавать имя env при запуске приложения?
notionquest
@notionquest Да, вы можете передать его, какng build --env=prod
Anjum ....
31

Хотя подход с использованием класса AppSettings со строковой константой в качестве ApiEndpoint работает, он не идеален, поскольку мы не сможем поменять эту реальную точку ApiEndpoint на некоторые другие значения во время модульного тестирования.

Мы должны иметь возможность внедрять эти конечные точки API в наши сервисы (подумайте о внедрении сервиса в другой сервис). Нам также не нужно создавать целый класс для этого, все, что мы хотим сделать - это вставить строку в наши сервисы, являющуюся нашей ApiEndpoint. Чтобы завершить отличный ответ по пиксельным битам , вот полный код того, как это можно сделать в 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
В настоящее время существует «официальный» способ выполнения рекомендаций угловой командой в их уроке. Я добавил ответ ниже: ( stackoverflow.com/a/40287063/1671558 )
Илья Черномордик
1
этот код больше не является точным, реализация этого приведет к тому, что ApiEndpoint не найден в AppComponent.
WilliamX
Итак, я не одинок. Вы знаете, какая версия это сломалось? Есть ли альтернативный способ, который не требует определения значений для глобального объекта, а затем их предоставления?
Дженс Бодал
29

Это мой недавний опыт с этим сценарием:

  • @ angular / cli: 1.0.0
  • узел: 6.10.2
  • @ angular / core: 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@

  }

Результат чистый, и на консоли нет предупреждений благодаря недавнему комментарию Джона Папы в этом выпуске:

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

Ключ реализован в другом файле интерфейса.

JavierFuentes
источник
см. также stackoverflow.com/a/43193574/3092596 - который в основном такой же, но создает инъекционные модули, а не провайдеров
goredwards
19

Все решения кажутся сложными. Я ищу простейшее решение для этого случая, и я просто хочу использовать константы. Константы просты. Есть что-нибудь, что говорит против следующего решения?

app.const.ts

'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/'}), ...]);
SnareChops
источник
1
Зачем вводить это? Думаю, в этом нет необходимости ... вы можете использовать его, как только импортируете. @SnareChops
Sasxa
@Sasxa Я согласен, хотя это может быть полезно для модульного тестирования и тому подобное. Просто пытаюсь дать полный ответ.
SnareChops
1
@Andreas Вы можете использовать constYest
SnareChops
Пожалуйста, предоставьте стаклиц этой работы. Я видел очень много примеров предоставления сервиса в методе начальной загрузки, но пока не нашел такого с достаточно работающим примером. Возможно, что-то изменилось в более поздней версии 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)
      }),

таможенно-typings.d.ts

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)});
}
occasl
источник
3

Использовать файл свойств, сгенерированный во время сборки, просто и легко. Это подход, который использует Angular CLI. Определите файл свойств для каждой среды и используйте команду во время сборки, чтобы определить, какой файл копируется в ваше приложение. Затем просто импортируйте файл свойств для использования.

https://github.com/angular/angular-cli#build-targets-and-environment-files

R.Creager
источник
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
  }
}

В папке приложения я добавляю папку config / 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не определяет константу в стандартном смысле.

Хотя он сам по себе является механизмом регистрации провайдера, его лучше понять в контексте функции related module.value( $provide.value). Официальная документация четко описывает вариант использования:

Зарегистрируйте службу значений с помощью инжектора $, такого как строка, число, массив, объект или функция. Это сокращение для регистрации службы, где свойство $ get ее провайдера является фабричной функцией, которая не принимает аргументов и возвращает службу значения. Это также означает, что невозможно внедрить другие сервисы в сервис с высокой стоимостью.

Сравните это с документацией для module.constant( $provide.constant), в которой также четко указан вариант использования (выделено мое):

Зарегистрируйте постоянную службу с помощью $ injector, например строки, числа, массива, объекта или функции. Как и значение, невозможно вставить другие сервисы в константу. Но в отличие от значения, константа может быть введена в функцию конфигурации модуля (см. Angular.Module) и не может быть переопределена декоратором AngularJS .

Следовательно, constantфункция AngularJS не обеспечивает константу в общепринятом значении термина в поле.

Тем не менее, ограничения, наложенные на предоставленный объект, вместе с его более ранней доступностью через $ 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 изменялась при определенных обстоятельствах.

Но в этом случае предоставление его в виде строкового литерала для представления одного URL-адреса constantфункции не сработало бы.

Лучший аргумент, и, вероятно, еще один, связанный с причиной существования $provide.constantфункции AngularJS, заключается в том, что, когда был введен AngularJS, в JavaScript не было стандартной концепции модуля. В этом случае глобальные переменные будут использоваться для обмена значениями, изменяемыми или неизменяемыми, и использование глобальных переменных проблематично.

Тем не менее, предоставление чего-то подобного через структуру увеличивает связь с этой структурой. Он также смешивает специфическую для Angular логику с логикой, которая будет работать в любой другой системе.

Это не значит, что это неправильный или вредный подход, но лично я, если мне нужна константа в приложении Angular 2, я напишу

export const π = 3.14159265;

так же, как я бы использовал AngularJS.

Чем больше вещи меняются ...

Алуан Хаддад
источник
0

Лучший способ создания констант всего приложения в Angular 2 - использование файлов environment.ts. Преимущество объявления таких констант состоит в том, что вы можете изменять их в зависимости от среды, поскольку для каждой среды может быть свой файл среды.

Хасан Арафат
источник
Это не работает, если вы собираетесь создать приложение один раз, а затем развернуть его в нескольких средах.
Йенс Бодал
@JensBodal: правда, у меня та же проблема. Использование файлов среды кажется чистым дизайном, за исключением того, что вы не можете использовать свою предварительную сборку для производства. Кроме того, это требует наличия ваших производственных параметров в среде разработки, что иногда было бы проблемой безопасности.
devC
-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);
  }
}

Вы можете использовать это либо в constructorили ngOnInit(){}, или в каких - либо методов предопределить.

Шубхам Верма
источник