Avatar ·

Как правильно определять глобальные константы в TypeScript для Angular приложения?

Всем привет! Я новичок в TypeScript и Angular и столкнулся с проблемой определения глобальных констант. У меня есть приложение, в котором используются одни и те же значения в разных компонентах и сервисах. Например, URL API, максимальное количество элементов на странице, названия событий и так далее. Сейчас я просто дублирую эти значения в каждом файле, но это явно неправильно — если нужно изменить URL, приходится править его в десятке мест. Пробовал объявить константы внутри класса, но тогда они недоступны из других файлов без импорта экземпляра класса. Вот пример того, что я делаю сейчас: ```typescript // в компоненте user.component.ts const API_URL = 'https://api.example.com/v1'; const MAX_ITEMS = 10; export class UserComponent { constructor() { console.log(API_URL); // работает } } // в другом компоненте product.component.ts const API_URL = 'https://api.example.com/v1'; // дублирование! ``` Я слышал про `const` и `readonly`, но не понимаю, как организовать константы так, чтобы они были доступны глобально, не засоряли пространство имен и были типобезопасными. Также меня интересует, как правильно работать с константами в Angular, учитывая dependency injection (DI). Можно ли использовать константы в провайдерах модулей? Или лучше создать отдельный сервис? Пожалуйста, подскажите best practices для TypeScript и Angular. Заранее спасибо! <<>>

Лучший подход: отдельный файл с константами и использование const enum

В TypeScript и Angular рекомендуется выносить все глобальные константы в отдельные файлы (обычно с расширением .constants.ts или .config.ts). Это обеспечивает единый источник правды (single source of truth) и упрощает поддержку кода.

Вот правильная реализация:

// app.constants.ts
export const enum ApiUrls {
  BASE = 'https://api.example.com/v1',
  USERS = '/users',
  PRODUCTS = '/products'
}

export const enum Pagination {
  MAX_ITEMS = 10,
  DEFAULT_PAGE = 1,
  PAGE_SIZE_OPTIONS = [5, 10, 20, 50]
}

export const enum Events {
  USER_LOGIN = 'user:login',
  USER_LOGOUT = 'user:logout',
  DATA_LOADED = 'data:loaded'
}

// Для строковых констант, которые не являются числами, лучше использовать обычный const
export const APP_NAME = 'MyAngularApp';
export const DEFAULT_LANGUAGE = 'ru';

Использование в компонентах и сервисах

Теперь вы можете импортировать эти константы в любом файле без дублирования:

// user.service.ts
import { Injectable } from '@angular/core';
import { ApiUrls, Pagination } from './app.constants';

@Injectable({ providedIn: 'root' })
export class UserService {
  private apiUrl = `${ApiUrls.BASE}${ApiUrls.USERS}`;

  getUsers(page: number = Pagination.DEFAULT_PAGE) {
    return this.http.get(`${this.apiUrl}?page=${page}&limit=${Pagination.MAX_ITEMS}`);
  }
}

Почему const enum лучше, чем обычный объект?

  • Производительность: const enum полностью инлайнится на этапе компиляции — в скомпилированном JavaScript не будет лишних объектов.
  • Типобезопасность: TypeScript будет проверять, что вы используете только существующие значения.
  • Автодополнение: IDE подскажет все доступные константы при вводе ApiUrls.
  • Неизменяемость: В отличие от объектов, enum нельзя случайно изменить во время выполнения.

Использование с Angular Dependency Injection

Если вам нужно внедрять константы через DI (например, для тестирования), используйте InjectionToken:

// app.config.ts
import { InjectionToken } from '@angular/core';

export interface AppConfig {
  apiUrl: string;
  maxItems: number;
  appName: string;
}

export const APP_CONFIG = new InjectionToken<AppConfig>('app.config');

export const DEFAULT_CONFIG: AppConfig = {
  apiUrl: 'https://api.example.com/v1',
  maxItems: 10,
  appName: 'MyAngularApp'
};
// app.module.ts
import { APP_CONFIG, DEFAULT_CONFIG } from './app.config';

@NgModule({
  providers: [
    { provide: APP_CONFIG, useValue: DEFAULT_CONFIG }
  ]
})
export class AppModule { }
// Использование в сервисе
@Injectable()
export class DataService {
  constructor(@Inject(APP_CONFIG) private config: AppConfig) {
    console.log(this.config.apiUrl); // 'https://api.example.com/v1'
  }
}

Рекомендации для больших проектов

  • Группируйте константы по смыслу (API, UI, бизнес-логика) в разные файлы.
  • Для конфигурации окружения (dev/prod) используйте environment.ts из Angular CLI.
  • Избегайте магических чисел — всегда давайте им осмысленные имена.
  • Для строковых констант, которые используются в шаблонах (например, CSS-классы), используйте const вместо enum.
<<>>

Альтернативный подход: статический класс с readonly полями

Если вам не нравится синтаксис enum или нужно больше гибкости (например, вычисляемые константы), можно использовать класс со статическими readonly полями:

// app.constants.ts
export class AppConstants {
  public static readonly API_URL = 'https://api.example.com/v1';
  public static readonly MAX_ITEMS = 10;
  public static readonly APP_NAME = 'MyAngularApp';

  // Вычисляемая константа
  public static readonly FULL_API_URL = `${this.API_URL}/api`;

  // Метод для получения динамических значений
  public static getApiVersion(version: number): string {
    return `${this.API_URL}/v${version}`;
  }

  // Приватный конструктор предотвращает создание экземпляров
  private constructor() {}
}

Использование:

import { AppConstants } from './app.constants';

console.log(AppConstants.API_URL); // 'https://api.example.com/v1'
console.log(AppConstants.getApiVersion(2)); // 'https://api.example.com/v2'

Когда это лучше?

  • Когда константы зависят друг от друга и нужны вычисления.
  • Когда нужно добавить методы для форматирования или преобразования.
  • Если вы привыкли к классическому ООП-подходу.

Недостаток: В отличие от const enum, класс не инлайнится, и в итоговом JavaScript останется объект класса, что может немного увеличить размер бандла. Однако для большинства проектов это не критично.

Выбирайте подход в зависимости от ваших потребностей: для простых констант используйте const enum, для сложной логики — статический класс или InjectionToken.