Визначте глобальні константи


258

У куті 1.x ви можете визначити константи, як це:

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

Що було б еквівалентом у Angular (з TypeScript)?

Я просто не хочу повторювати базовий URL API знову і знову у всіх своїх службах.

Відповіді:


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) приватний конфігуратор) {} Тут про це згадується: blog.thoughtram.io/angular/2016/05/23/…, але не чому.
Мукус

Я припускаю, що ви пропустили якесь ключове слово імпорту чи експорту чи щось подібне, оскільки я використовую його з інтерфейсом, і, як ви говорите, дуже важливо мати його явно статично введені. Можливо, вам тут потрібно вказати точний виняток.
Ілля Чорномордик

46
Жодне з цих рішень, навіть рекомендований командним підходом, не виглядає елегантно. Чому намагаються створити константи громіздким процесом в куті 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 еквівалентно useClassAngular2.

app.factoryв Angular1 еквівалентно useFactoryAngular2.

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

2
Я насправді хотів би, щоб мої налаштування були у зовнішньому файлі, наприклад: settings.ts Як виглядав би цей файл?
AndreFeijo

Чи розглядали ви на сервері javascript на зразок NodeJS?
пікселі

5
Вибачте, я не зрозумів, як я буду вводити його в свою послугу? Оскільки я використовую зовнішній файл, чи потрібно його експортувати?
AndreFeijo

Я б зробив це частиною вашого процесу конфігурації збірки. тобто на основі вашого оточення, компілюйте / пакуйте різні файли разом, а потім розгортайте. Все це ви можете зробити з NodeJS за допомогою відповідних модулів.
пікселів

1
На жаль, NodeJS - це не варіант.
AndreFeijo

59

У Angular 4 ви можете використовувати клас навколишнього середовища, щоб зберегти всі ваші глобалі.

Ви за замовчуванням у вас середовище.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 це тому, що ти намагався ввести його. Цей підхід не використовує інжектор
Aluan Haddad

Створити подібну константу найпростіше. Я думаю, що протилежний аргумент втратити DI і тим самим втратити testability / mockValue - це деякий час завищено. У типовому додатку ми використовуємо стільки компонентів, що не належать до DI, як (RxJS), не турбуючи перевірку.
Амітеш

54

Оновлено для кутових 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.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)
  }
 }

6
Це не працює, як констант. Значення константи завжди однакове. У вашому випадку ваше API_ENDPOINTзначення може бути перезаписано в будь-який момент часу. Якщо this.ConstantService.API_ENDPOINT = 'blah blah'декларується в класі будь-коли після імпорту вашої так званої "константи" constant-service, новим значенням API_ENDPOINT буде 'blah blah'. Ваше рішення просто показує, як отримати доступ до змінної за допомогою сервісу, а не за допомогою константи.
Devner

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

Це мій останній досвід із цим сценарієм:

  • @ кутова / клі: 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@

  }

Результат чистий, на консолі немає попереджень, дякую останнім коментарем Джона Папи у цьому випуску:

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

Ключ був реалізований в іншому файлі інтерфейсом.


див. також 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/'}), ...]);

1
Навіщо його вводити? Думаю, у цьому немає потреби ... Ви можете використовувати його, як тільки імпортуєте. @SnareChops
Сакса

@Sasxa Я згоден, хоча це може бути добре для тестування одиниць і подібних. Просто намагаюся дати повну відповідь.
SnareChops

1
@Andreas Ви можете використовувати constyest
SnareChops

Укажіть, будь ласка, цей блок роботи. Я бачив так багато прикладів надання послуги методом завантаження, але ще не знайшов достатнього робочого прикладу. Можливо, щось змінилося в більш новій версії кутового.
Єнс Бодал

4

Якщо ви використовуєте Webpack , який я рекомендую, ви можете налаштувати константи для різних середовищ. Це особливо цінно, коли у вас різні постійні значення на основі середовища.

Ви, ймовірно, матимете кілька файлів /configвебпакету у своєму каталозі (наприклад, 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)
      }),

custom-typping.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)});
}

3

Використовувати файл властивостей, що генерується під час збирання, просто та легко. Це підхід, який використовує Angular CLI. Визначте файл властивостей для кожного середовища та використовуйте команду під час збирання, щоб визначити, який файл буде скопійовано у вашу програму. Потім просто імпортуйте файл властивості для використання.

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


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

У папку додатків я додаю конфігурацію папок / settings.json

Вміст у setu.json

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

У модуль додатку додайте APP_INITIALIZER

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

таким чином я можу легше змінити значення у файлі json. Я також використовую цей спосіб для постійних повідомлень про помилки / попередження.


0

AngularJS's module.constant не визначає константу в стандартному сенсі.

Хоча він стоїть самостійно як механізм реєстрації провайдера, його найкраще розуміти в контексті пов'язаної module.value( $provide.value) функції. В офіційній документації чітко зазначено випадок використання:

Зареєструйте послугу значень за допомогою інжектора $, такого як рядок, число, масив, об'єкт або функція. Це коротке значення для реєстрації послуги, де властивість $ get свого постачальника - це фабрична функція, яка не бере аргументів і повертає послугу цінностей. Це також означає, що неможливо вводити інші послуги в ціннісну послугу.

Порівняйте це з документацією для module.constant( $provide.constant), де також чітко зазначено випадок використання (міна акценту):

Зареєструйте постійну службу за допомогою інжектора $, такого як рядок, число, масив, об'єкт або функція. Як і значення, не можна вводити інші послуги в константу. Але на відміну від значення, константа може бути введена у функцію конфігурації модуля (див. Angular.Module), і її не можна перекрити декоратором AngularJS .

Тому constantфункція AngularJS не забезпечує константу в загальновизнаному значенні цього терміна в полі.

Враховуючи це, обмеження, розміщені на наданому об'єкті, а також його попередня доступність через інжектор $, явно говорить про те, що назва використовується аналогічно.

Якби ви хотіли фактичної константи в програмі AngularJS, ви б "надали" так само, як і в будь-якій програмі JavaScript, яка є

export const π = 3.14159265;

У Angular 2 застосовується та сама техніка.

Кутові програми 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 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);
  }
}

Ви можете використовувати це або в constructorабо ngOnInit(){}, або в яких - або методів визначити.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.