Сервисы и DI

f

Когда речь заходит о сервисах и Dependency Injection (DI) в Angular, обычно все сводят к тому, что это «мощно и гибко». Но на практике разработчик сталкивается с дилеммой: какую стратегию DI выбрать под свою конкретную задачу? Ошибка здесь стоит дорого — от раздутого кода до неработающих тестов.

Этот материал — не пересказ документации. Мы разберём, какие подходы к сервисам и DI работают для разных аудиторий: от новичка, собирающего своё первое приложение, до команды, поддерживающей enterprise-систему с тысячью модулей.

Кому подойдёт стандартный сервис на @Injectable с providedIn: 'root'

Это классика. Один экземпляр сервиса на всё приложение. Идеально, когда у вас нет сильной потребности в изоляции — например, сервис логирования, общий кэш или централизованное хранилище данных.

На практике 70% сервисов в Angular-проектах — это именно «корневые». Проблема начинается, когда разработчик по инерции делает все сервисы корневыми, хотя некоторые должны быть локальными. Типичный пример — сервис для работы с корзиной интернет-магазина. Если объявить его providedIn: 'root', то данные корзины будут общими для всех пользователей одного сеанса, что может вызвать путаницу в админке с просмотром чужих корзин.

Сервисы уровня NgModule: для крупных модульных приложений

Здесь мы указываем провайдеров в декораторе @NgModule({ providers: [...] }). Каждый экземпляр сервиса живёт ровно в контексте своего модуля — разным модулям можно внедрить разные реализации.

Это уже продвинутый уровень. Представьте, что у вас в приложении есть два раздела: «Каталог товаров» и «Панель администратора». В разделе «Каталог» сервис должен показывать все товары (включая скрытые для обычных пользователей), а в админке — только те, что редактирует текущий сотрудник. С корневым сервисом этого не сделать. Только через провайдеры на уровне модуля.

Локальные провайдеры на компоненте: для виджетов и многократных инстансов

Когда один общий сервис не подходит, но и модульный уровень слишком груб, провайдеры на компоненте приходят на помощь. Просто добавляете providers: [MyService] в декоратор @Component, и каждый компонент получает свой экземпляр сервиса.

Это правда жизни для виджетов на одной странице. Например, на микрофинансовой платформе есть 5 разных графиков продаж, каждый со своим набором данных. Если сделать сервис общего состояния, они будут конфликтовать. Решение — каждый компонент графика имеет свой ленгвист сервиса для загрузки данных.

Использование InjectionToken: когда интерфейсы не имеют декоратора

Классический сервис на @Injectable — не единственный путь. Если вы передаёте примитив, конфигурационный объект или интерфейс (который не является инъектируемым), используйте InjectionToken. Это фабрика для создания провайдеров по контракту, а не по классу.

Частая ошибка новичков: они пытаются внедрить строку или объект конфигурации напрямую через DI — получают тоскливый выброс NullInjectorError. Решение — создание токена с описанием провайдера. Например, токен для передачи API-ключа в сервис: const API_KEY = new InjectionToken<string>('api-key');

Нестандартный случай: DI с forwardRef и циклическими зависимостями

Наконец, есть пик мастерства — разрешение циклических зависимостей. Когда сервис A ссылается на сервис B, а B — обратно на A, Angular падает с «circular dependency».

Рецепт от профи: используйте forwardRef, чтобы сообщить загрузчику, что зависимость будет определена позже. Это костыль? Частично. Но в сложных работах, где shared-сервисы общаются друг с другом (как в модели domain-driven design), без forwardRef не обойтись.

Итог: как выбрать свой путь

Выбор стратегии DI в Angular не техническая прихоть — это архитектурное решение, влияющее на поддержку и развитие продукта в 2026 году.

Резюмируем: если вы инди-разработчик, берите providedIn: 'root' и не парьтесь.

Если работаете в команде над автономными модулями — освойте провайдеры на уровне NgModule.

Если делаете динамические интерфейсы с повторяющимися независимыми виджетами — компонентный уровень DI станет вашим спасением.

Ну а для продвинутых кейсов запомните про InjectionToken и forwardRef.

И вот ключевое отличие этой страницы от других курсов: мы не просто описываем DI, а привязываем его к профилю разработчика — кто, когда и для чего использует каждый вариант. Другие ресурсы дают общую теоретическую базу, а здесь вы видите конкретные сегменты аудитории и критерии выбора. Больше никакого абстрактного «внедрение зависимостей».

Добавлено: 23.04.2026