Angular2: Opaque токены и мультипровайдеры

При определении провайдеров рано или поздно у всех у нас возникает вопрос “А что если будет 2 провайдера с одним и тем же именем?” – Ну тут все довольно просто: последний определенный перезатрет все определенные до него.

Но что если вы подключаете сторонний модуль, а в нем уже определен провайдер с таким же именем? Очевидно, что вы его перезатрете своим. Как избежать этого?

Opaque токен

На помощь нам спешит opaque токен, который выглядит немного как костыль архитектуры. Чтобы избежать коллизии, мы заворачиваем имя нашего сервиса в объект с помощью класса OpaqueToken:

const MY_HTTP_TOKEN: OpaqueToken = new OpaqueToken('Http');

Если мы посмотрим на исходный код этого класса, то увидим всего одну строчку реализации:

export class OpaqueToken {
  constructor(protected _desc: string) {}

  toString(): string { return `Token ${this._desc}`; }
}

То есть мы просто  заворачиваем наше название в объект, который в случае чего его вернет.

Что это дает? Какое бы мы дурацкое уникальное имя не придумали для своего провайдера, все равно есть вероятность, что кто-то придумает что-то подобное. А вот при создании нового объекта мы всегда гарантируем что он будет уникальный. И теперь коллизии нам не страшны – можем спокойно внедрять нашу сущность:

constructor(@Inject(MY_HTTP_TOKEN) private myHttp){

 

Мультипровайдеры

Но представим другую ситуацию: вам внезапно захотелось запихнуть в провайдер несколько классов. Зачем? Ну вот захотелось. И чтобы для каждого класса отрабатывал механизм внедрения зависимости и получал сущность. И, да, разработчики предусмотрели и такой случай. Для этого вам нужно использовать мультипровайдеры, а именно специальное свойство multi при определении провайдера:

  providers: [
  { provide: 'SuperProvider', useClass: class A {}, multi: true },
  { provide: 'SuperProvider', useClass: class B {}, multi: true}]

И теперь можем инжектить два-в-одном:

 constructor(@Inject('SuperProvider') private testInjection) {

и получить в testInjection массив из 2х сущностей: экземпляров класса A и B.

Так, еще раз – зачем это нужно? Если вы пишете библиотеку/плагин и хотите сделать его расширяемым для разработчиков, которые будут подключать ваше решение в свои приложения, думаю это именно то, что вам нужно. Именно так реализованы 2 сервиса хранящие валидаторы в ангуляре, а именно NG_VALIDATORS и NG_ASYNC_VALIDATORS, что позволяет вам добавить свои валидаторы в стандартную коллекцию:

{ provide: NG_VALIDATORS, useValue: (formControl) => {}, multi: true }

По прежнему остается вопрос: зачем использовать задание провайдеров через строки, если это может привести к коллизии? Оставим его на совести разработчиков Angular 2.