Спасибо большое за комментарий! Вы не первый, кто написал о том, что не хватает реальных практических примеров для демонстрации подхода. Я учту это в своих будущих работах.
Все-таки multi существует для того, когда есть задача получить по ключу именно массив, а не одну сущность.
Согласен. Стоило упомянуть привычные кейсы использования multi, такие как интерсепторы или VALUE_ACCESSOR, для более подробного описания темы. Однако в данной статье речь больше про кейсы, когда требуется регистрировать список различных реализаций и уметь получать необходимую реализацию, в зависимости от внешних факторов.
С этим я согласен, вот только multi тут ни к чему. Мы можем переопределять реализацию зависимости на разных уровнях иерархии компонентов...
Речь идёт о регистрации реализаций в один скоуп в иерархии для дальнейшего взаимодействия с ними, как вы написали чуть ниже. Давайте приведу несколько примеров, где это может оказаться удобным.
Пример 1
Рис 1 - Функционал конструктора форм
Представим, что мы разрабатываем конструктор форм (для примера взят скриншот с яндекс форм). В данном случае мы создам некоторый контракт IWidgetConfigurator, который будет иметь поле key и прочие общие методы для работы с виджетом. Для каждого виджета будет создана своя реализация данного интерфейса, которая будет предоставлять специфические данные, настройки, модели для конкретного виджета (можно придумать разные отвественности для этого сервиса). Во время драг-н-дропа виджета из сайдбара на форму у нас будет появляться компонента данного виджета (в примере "Один вариант"). Все эти виджеты будут унаследованы от базовой компоненты виджета и иметь некоторое абстрактное поле type, которое будет реализовано в наследниках. По этому полю type мы сможем получать из сервиса-менеджера необходимую реализацию для настройки активного виджета.
Пример 2
За вторым примером далеко ходить не пришлось — конструтор параграфа на хабре тоже мог бы быть реализован с помощью данного подхода.
Рис 2 - Функционал конструктора параграфа
В данном случае у нас есть компонента конструктора параграфа, в которую мы можем зарегистрировать список виджетов. Каждый виджет будет реализовывать некоторый интерфейс.
Также есть селект-контрол, который содержит список опций в форматe { имя_виджета, ключ_виджета }. Когда мы кликаем на опцию селекта происхдит valueChanges с новым ключом и мы можем добавить виджет на параграф, выбрав реализацию из менеджера по ключу виджета.
const provideTextWidgets = provideMultiService.bind(null, {
managerType: TextWidgetsManager,
serviceToken: TEXT_WIDGET_TOKEN,
registerEach: false,
services: [
new QuoteTextWidget(),
new OrderedListTextWidget(),
new ImageTextWidget(),
new CodeTextWidgetd(),
// ...,
new MyCoolTextWidget(),
],
});
@Component({
providers: [
...provideTextWidgets()
],
})
class ParagraphConstructorComponent implements OnInit {
private readonly select: FormControl = new FormControl<string | null>(null);
private readonly paragraphModel: ParagraphModel = new ParagraphModel();
private readonly _widgetsManager: TextWidgetsManager = inject(TextWidgetsManager);
public ngOnInit(): void {
this.select.valueChanges
.pipe(
takeUntil()
)
.subscribe((key: WidgetType) => {
this._widgetsManager.get(key).addWidgetTo(this.paragraphModel);
});
}
}
В данных случаях использоавние описанного подхода считаю достаточно уместным, оправданным и удобным.
Под один токен регистрируются разные реализации одного контракта, которые могут подменяться в зависимости от состояния приложения. Если бы у вас было три реализации и каждая была бы зарегистрированна отдельно, вто в коде вам бы приходилось получать все три релизации, а затем определять которую из них использовать.
А разве не то же самое реализует механизм DI, используемый ангуляром?
В ангуляре реализуется паттерн Injection Key, который позволяет регистрировать зависимости в DI-контейнер по уникальному ключу, а затем получать их с помощью этого ключа. Однако, возникают ситуации, что по одному ключу регистрируется список зависимостей с помощью опции провайдера multi: true. Такой подход подразумевает, что мне нужно как-то получать конкретную реализацию из списка. То есть связь между ключём и значением становится не one to one, а one to many.
В чем практический смысл?
Представим, что у меня есть 10 сервисов, которые я регистрирую, через multi.
Если я попытаюсь сделать inject(EXAMPLE_SERVICE_TOKEN), то вместо одного инстанса получу список инстансов. Но как мне выбрать нужный инстанс из списка? Допустим у меня есть некоторый select-control, и я хочу, чтобы из DI бралась новая реализация, каждый раз, когда отрабатывает valueChanges, в зависимости от значения селекта.
Как раз для этого мне будет удобно использовать сервис-менеджер, чтобы по ключу получать конкретный сервис из списка.
И как понимать: какие сервисы зарегистрированны, а какие - нет?
Например можно создать в сервисе-менеджере метод has(key: TKey): boolean, который будет говорить зарегистрирован ли сервис по такому ключу или нет. Для этой задачи можно придумать множество решений и расширить базовый функционал.
Спасибо большое за комментарий! Вы не первый, кто написал о том, что не хватает реальных практических примеров для демонстрации подхода. Я учту это в своих будущих работах.
Согласен. Стоило упомянуть привычные кейсы использования
multi
, такие как интерсепторы илиVALUE_ACCESSOR
, для более подробного описания темы. Однако в данной статье речь больше про кейсы, когда требуется регистрировать список различных реализаций и уметь получать необходимую реализацию, в зависимости от внешних факторов.Речь идёт о регистрации реализаций в один скоуп в иерархии для дальнейшего взаимодействия с ними, как вы написали чуть ниже. Давайте приведу несколько примеров, где это может оказаться удобным.
Пример 1
Представим, что мы разрабатываем конструктор форм (для примера взят скриншот с яндекс форм). В данном случае мы создам некоторый контракт
IWidgetConfigurator
, который будет иметь полеkey
и прочие общие методы для работы с виджетом. Для каждого виджета будет создана своя реализация данного интерфейса, которая будет предоставлять специфические данные, настройки, модели для конкретного виджета (можно придумать разные отвественности для этого сервиса). Во время драг-н-дропа виджета из сайдбара на форму у нас будет появляться компонента данного виджета (в примере "Один вариант"). Все эти виджеты будут унаследованы от базовой компоненты виджета и иметь некоторое абстрактное полеtype
, которое будет реализовано в наследниках. По этому полюtype
мы сможем получать из сервиса-менеджера необходимую реализацию для настройки активного виджета.Пример 2
За вторым примером далеко ходить не пришлось — конструтор параграфа на хабре тоже мог бы быть реализован с помощью данного подхода.
В данном случае у нас есть компонента конструктора параграфа, в которую мы можем зарегистрировать список виджетов. Каждый виджет будет реализовывать некоторый интерфейс.
Также есть селект-контрол, который содержит список опций в форматe
{ имя_виджета, ключ_виджета }
. Когда мы кликаем на опцию селекта происхдитvalueChanges
с новым ключом и мы можем добавить виджет на параграф, выбрав реализацию из менеджера по ключу виджета.В данных случаях использоавние описанного подхода считаю достаточно уместным, оправданным и удобным.
Под один токен регистрируются разные реализации одного контракта, которые могут подменяться в зависимости от состояния приложения. Если бы у вас было три реализации и каждая была бы зарегистрированна отдельно, вто в коде вам бы приходилось получать все три релизации, а затем определять которую из них использовать.
Спасибо за отзыв! Постараюсь добавлять больше конкретных примеров в будущем.
В ангуляре реализуется паттерн Injection Key, который позволяет регистрировать зависимости в DI-контейнер по уникальному ключу, а затем получать их с помощью этого ключа. Однако, возникают ситуации, что по одному ключу регистрируется список зависимостей с помощью опции провайдера
multi: true
. Такой подход подразумевает, что мне нужно как-то получать конкретную реализацию из списка. То есть связь между ключём и значением становится не one to one, а one to many.Представим, что у меня есть 10 сервисов, которые я регистрирую, через multi.
Если я попытаюсь сделать
inject(EXAMPLE_SERVICE_TOKEN)
, то вместо одного инстанса получу список инстансов. Но как мне выбрать нужный инстанс из списка? Допустим у меня есть некоторый select-control, и я хочу, чтобы из DI бралась новая реализация, каждый раз, когда отрабатываетvalueChanges
, в зависимости от значения селекта.Как раз для этого мне будет удобно использовать сервис-менеджер, чтобы по ключу получать конкретный сервис из списка.
Например можно создать в сервисе-менеджере метод
has(key: TKey): boolean
, который будет говорить зарегистрирован ли сервис по такому ключу или нет. Для этой задачи можно придумать множество решений и расширить базовый функционал.