Как правильно определять глобальные константы в 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.