Перейти к содержанию

Настройка поставщиков зависимостей

📅 2.08.2022

В теме Создание и внедрение сервисов описывается использование классов в качестве зависимостей. Помимо классов, в качестве зависимостей можно использовать и другие значения, такие как булевы, строки, даты и объекты. Angular DI предоставляет необходимые API для гибкой настройки зависимостей, поэтому вы можете сделать эти значения доступными в DI.

Указание маркера провайдера

Если вы укажете класс сервиса в качестве маркера поставщика, инжектор по умолчанию будет инстанцировать этот класс с помощью оператора new.

В следующем примере класс Logger предоставляет экземпляр Logger.

1
providers: [Logger];

Однако вы можете настроить DI на использование другого класса или любого другого значения для ассоциации с классом Logger. Таким образом, когда Logger будет инжектирован, вместо него будет использоваться это новое значение.

Фактически, синтаксис провайдера класса — это сокращенное выражение, которое расширяется в конфигурацию провайдера, определяемую интерфейсом Provider.

Angular расширяет значение providers в данном случае в полный объект провайдера следующим образом:

1
[{ provide: Logger, useClass: Logger }];

Расширенная конфигурация провайдера представляет собой объект-букварь с двумя свойствами:

  • Свойство provide содержит токен, который служит ключом для поиска значения зависимости и настройки инжектора.
  • Второе свойство — это объект определения провайдера, который указывает инжектору, как создать значение зависимости. Ключ определения провайдера может быть одним из следующих:
    • useClass — этот параметр указывает Angular DI инстанцировать предоставленный класс при инжектировании зависимости
    • useExisting — позволяет использовать псевдоним маркера и ссылаться на любой существующий маркер.
    • useFactory — позволяет определить функцию, которая конструирует зависимость.
    • useValue — предоставляет статическое значение, которое должно быть использовано в качестве зависимости.

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

Провайдеры классов: useClass

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

В следующем примере класс BetterLogger будет инстанцирован, когда зависимость Logger будет запрошена в компоненте или любом другом классе.

1
[{ provide: Logger, useClass: BetterLogger }];

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

1
2
3
4
[
    UserService,
    { provide: Logger, useClass: EvenBetterLogger },
];

В этом примере EvenBetterLogger отображает имя пользователя в сообщении журнала. Этот логгер получает пользователя из инжектированного экземпляра UserService.

1
2
3
4
5
6
7
8
9
@Injectable()
export class EvenBetterLogger extends Logger {
  constructor(private userService: UserService) { super(); }

  override log(message: string) {
    const name = this.userService.user.name;
    super.log(`Message to ${name}: ${message}`);
  }
}

Angular DI знает, как построить зависимость UserService, поскольку она была настроена выше и доступна в инжекторе.

Псевдоним провайдера: useExisting

Ключ провайдера useExisting позволяет сопоставить один токен с другим. По сути, первый токен является псевдонимом для сервиса, связанного со вторым токеном, создавая два способа доступа к одному и тому же объекту сервиса.

В следующем примере инжектор внедряет экземпляр синглтона NewLogger, когда компонент запрашивает либо новый, либо старый логгер. Таким образом, OldLogger является псевдонимом для NewLogger.

1
2
3
4
5
[
    NewLogger,
    // Alias OldLogger w/ reference to NewLogger
    { provide: OldLogger, useExisting: NewLogger },
];

Убедитесь, что вы не переименовываете OldLogger в NewLogger с помощью useClass, так как это создаст два разных экземпляра NewLogger.

Провайдеры фабрик: useFactory

Ключ провайдера useFactory позволяет вам создать зависимый объект путем вызова фабричной функции. При таком подходе вы можете создать динамическое значение на основе информации, доступной в DI и в других местах приложения.

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

Чтобы сохранить чувствительную к безопасности информацию в UserService и вне HeroService, задайте конструктору HeroService булев флаг для управления отображением секретных героев.

1
2
3
4
5
6
7
8
9
constructor(
  private logger: Logger,
  private isAuthorized: boolean) { }

getHeroes() {
  const auth = this.isAuthorized ? 'authorized ' : 'unauthorized';
  this.logger.log(`Getting heroes for ${auth} user.`);
  return HEROES.filter(hero => this.isAuthorized || !hero.isSecret);
}

Чтобы реализовать флаг isAuthorized, используйте фабрику-провайдер для создания нового экземпляра логгера для HeroService.

1
2
3
4
const heroServiceFactory = (
    logger: Logger,
    userService: UserService
) => new HeroService(logger, userService.user.isAuthorized);

Функция factory имеет доступ к UserService. Вы инжектируете Logger и UserService в провайдер фабрики, чтобы инжектор мог передать их функции фабрики.

1
2
3
4
5
export const heroServiceProvider = {
    provide: HeroService,
    useFactory: heroServiceFactory,
    deps: [Logger, UserService],
};
  • Поле useFactory указывает, что провайдер является фабричной функцией, реализация которой — heroServiceFactory.
  • Свойство deps представляет собой массив маркеров провайдера.

Классы Logger и UserService служат маркерами для своих собственных классов-провайдеров. Инжектор разрешает эти маркеры и вводит соответствующие сервисы в соответствующие параметры фабричной функции heroServiceFactory.

Захват провайдера фабрики в экспортируемой переменной heroServiceProvider делает провайдер фабрики многократно используемым.

Провайдеры значений: useValue

Ключ useValue позволяет связать фиксированное значение с маркером DI. Используйте эту технику для предоставления констант конфигурации во время выполнения, таких как базовые адреса веб-сайтов и флаги функций. Вы также можете использовать провайдер значения в модульном тестировании для предоставления имитационных данных вместо производственной службы данных. В следующем разделе представлена дополнительная информация о ключе useValue.

Использование объекта InjectionToken

Определите и используйте объект InjectionToken для выбора маркера провайдера для неклассовых зависимостей. В следующем примере определен токен APP_CONFIG типа InjectionToken.

1
2
3
4
5
import { InjectionToken } from '@angular/core';

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

Необязательный параметр type, <AppConfig>, и описание токена, app.config, определяют назначение токена.

Далее зарегистрируйте провайдер зависимостей в компоненте, используя объект InjectionToken из APP_CONFIG.

1
2
3
providers: [
    { provide: APP_CONFIG, useValue: HERO_DI_CONFIG },
];

Теперь внедрите объект конфигурации в конструктор с помощью декоратора параметров @Inject().

1
2
3
constructor(@Inject(APP_CONFIG) config: AppConfig) {
  this.title = config.title;
}

Интерфейсы и DI

Хотя интерфейс TypeScript AppConfig поддерживает типизацию внутри класса, интерфейс AppConfig не играет никакой роли в DI. В TypeScript интерфейс является артефактом времени проектирования и не имеет представления во время выполнения, или маркера, который может использовать система DI.

Когда транспилятор меняет TypeScript на JavaScript, интерфейс исчезает, потому что в JavaScript нет интерфейсов.

Поскольку у Angular нет интерфейса, который он мог бы найти во время выполнения, интерфейс не может быть маркером, и вы не можете его внедрить.

1
2
// Can't use interface as provider token
[{ provide: AppConfig, useValue: HERO_DI_CONFIG })]
1
2
// Can't inject using the interface as the parameter type
constructor(private config: AppConfig){ }

Что дальше

Комментарии