Как стать автором
Обновить

Комментарии 25

Выгода от использования провайдеров в данном случае сомнительна.
По-моему, нужно идти в другую сторону — к упрощению архитектуры проекта, а не придумывать 100500 волшебных способов решения проблем, которых вообще не должно возникать на frontend-е.
Можете более информативно пояснить, какое упрощение можно сделать в данном кейсе? Ситуация распространённая: в зависимости от роута подгружать данные по id.
Я как раз про то, что получение данных по роуту — это совершенно стандартная задача. И для решения этой задачи предлагается метод, который, по-большому счету, ничего не меняет, а размазывает логику ещё по нескольким сущностям. И при этом усложняет понимание проекта новыми разработчиками, которые (давайте будем честны) будут тупо копипастить, ибо «тут так заведено».

Упрощение архитектуры в данном случае вижу в максимальном упрощении и ускорении получения информации об организации. Скорее всего, это реализуется силами backend-а, кешированием и прочим.
Я всё равно не понимаю, что вы предлагаете упростить со стороны SPA или конкретного компонента. В статье приведено решение в лоб — дергаем сервис при изменении orderId.

После манипуляций с провайдерами, компоненту вообще стало пофиг откуда организации берутся и как обновляются. Компонент теперь зависит только от Организаций, а не способа их получения. (Я предпочитаю использовать State + Resolver, чтобы на роут юзера перебрасывало только после загрузки данных. Но это дело даже не вкуса, а UX.)

Другое дело, что boilerplate не очень приятный получается вокруг провайдеров, но это отдельная проблема.
Я за то, чтобы не усложнять. А предложенный здесь способ, по-моему, ничего существенного не делает и лишь усложняет приложение.
Простите, но вы пишете ерунду. Автор достаточно подробно описывает плюсы вынесения обработки получения данных из кода компонента в код провайдера. Это реально делает код намного чище и проще в поддержке.

Я участвовал во нескольких больших проектах на ng и видел слишком много примеров, когда код компонента крайне усложняется тем, что нужно по каким-то параметрам получить данные из нескольких источников. Три уровня вложенности .subscribe() — лишь меньшее из виденных ужасов.
Часто нужно обновить organization. Как это сделать в случае с провайдером?
Если хотим сделать компонент для редактирования организации, то я бы сделал взаимодействие с сервисом в компоненте. Тогда компонент зависит не от данных организации, а именно от сервиса — ему нужно уметь получить данные, ему нужно уметь обновить их, удалить и т.п.

У нас провайдеры хорошо упрощают код в кейсах, когда компонент зависит именно от данных. Для других случаев мы используем как обычные сервисы, так и разные вроде Observabled-based или Subject-based варианты, как в статье, ссылка на которой в комментарии ниже
Можно не регистрировать токен и не использовать фабрику, а создать инжектируемый класс унаследовав его от Observable, как предлагал ваш коллега в одной из прошлых статей. тут habr.com/ru/company/tinkoff/blog/501084

Ну или еще проще, обычный подчиненный сервис, который инжектит локальный роут и апи-сервис и предоставляет метод для получения сущности, зависимой от роута.
Это чище читается, чем FactoryProvider.

Но могу сказать что у этого подхода есть и минус. При чтении кода не получится быстро определить, какие сущности зависят от роута, приходится перебирать все сервисы и смотреть где что инжектится.
Возможно самый читабельный вариант это просто
selectOrganizatons(route: ActivatedRoute): Observable<Organization[]>
да, все верно :)
Приведенный в статье кейс очень простой, но это плата за лаконичность статьи. Сложный кейс мог бы дать один конкретный хороший пример, но замылыть суть самой идеи. Я выбрал первый вариант

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

Возможно, не до конца вышло донести мысль, что это лишь один из альтернативных подходов и точно не заменяющий все остальные :(
нормуль.
Я б еще добавил что inject имеет ограниченную область применения.
Must be used in the context of a factory function such as one defined for an InjectionToken.

И не стоит путать с одноименной функцией из @angular/core/testing
Привожу пример, как по мне поинтереснее из своей практики.
Решали следующую логику: Есть таблица данных(или иное представление), с возможностью перехода в форму элемента. Переход может осуществляться как полный(средствами роутера), в модальном окне, сайд баром — определяется настройками пользователем. В одной точке в зависимости от настроек происходит подмена сервиса, который выполняет переходы. Тем самым избавляем необходимость проталкивать логику выбора в другие компоненты. Подмена поведения логики с помощью DI одна из сильных качеств)
Спасибо за статью, буквально вчера сделал так как «делать не нужно»,- будет повод исправить
Как вы применяете данный подход при работе с формами (например Reactive Forms)?

Обычно формы живут в рамках одного компонента, поэтому с ними таких вопросов не возникает.


Бывают случаи сложнее, когда форма большая и хочется ее разнести. Помню был случай, когда сложный компонент вроде выпадающего календаря нуждался в данных контрола, но с дефолтным значением. Тоже решали частными провайдерами и вот такой штукой в useFactory: https://twitter.com/marsibarsi/status/1269201441756979200

я иногда оформляю форму как Injectable класс, наследуемый от FormGroup.
Лежит в отдельном файлике, прилетает в компонент как провайдер.
Хотя может это и неправильно, все таки форма это часть компонента-представления.

Сколько не читал статей о DI, всё равно для меня не понятно как это всё работает. Фабрики какие-то, инжекторы. Я в observable быстрее въехал и активно использую. Может когда-то пойму…

Ага, у меня было так, что DI шел тяжеловато, но когда разобрался, то полюбил :)

Могу порекомендовать тут скринкаст Степана Суворова на ютюбе, там базовые штуки: www.youtube.com/watch?v=bJvBRJUytgg&list=PLDyvV36pndZF-vwsVB48ivZyNJ4ETBKNY&index=14

Более фундаментально про саму концепцию IoC и конкретно DI есть интересная серия видео у Ильи Климова:
www.youtube.com/watch?v=ETyltCwtQHs&list=PLvTBThJr861xKTf1x6P49MwN6yoN4v69k

На почитать могу собственный материал порекомендовать: первая глава в angular.institute. Только там на английском, надеюсь не будет проблемой
Большое спасибо. На английском не проблема. Надеюсь после этого точно пойму
В примере ORGANIZATION_PROVIDERS — это массив провайдеров.
Далее в компоненте в массив providers просто добавляется ORGANIZATION_PROVIDERS. Без spread-оператора.
Это работает, т.к. providers видимо поддерживает работу с массивами.
Проблема вылезает, когда нужно сделать тест на такой компонент и переопределить провайдер: overrideComponent похоже не поддерживает массивы в providers. Со spread'ом работает.

Вопрос, собственно, следующий — не правильнее ли тогда везде использовать массив провайдеров со spread? Как вы решали подобные проблемы?
хм, не попадался в такой кейс c override, спасибо

Мы копались внутри Angular, providers там «съедает, что дадут»: можно один отдельный провайдер, можно массив провайдеров, можно массив с массивом с массивом с провайдером — тоже сработает
Первое! Автору спасибо за вложенные усилие и публикацию статьи

Вопросы

Автору
1) Обычно выносят в файлы тот код который планируют переиспользовать — я правильно понимаю что этот код(провайдер) будет использоваться только в одном компоненте?
2) С коллегами пообсуждали статью и возник еще вопрос. А не будет ли проблем с отловом ошибок если прописали не тот интерфейс в фабрике или месте инжектирования
3) не будет ли сложно поддерживать/использовать такой код?

Комментаторам
1) инжектируя работу роут как параметр разве мы не увеличиваем связанность разных классов — веть сервисы нужны для работы с данными/бизнесовой логикой (то есть даем ему id и получаем список организаций для этого id, сервис не должен знать что такое роутер)

Отвергаешь предлагай
собственно предложить нечего =(
1) разве что сделать какой то базовый компонент для всех компонентов-контейнеров в котором будет инжектиться роутер и стоять геттер на параметры роута (такой метод затрудняет понимание кода, и не стоит того)
Спасибо за такой развернутый комментарий :)
Попробую ответить на все вопросы одним сообщением, а то у меня один ответ залезает на другой:
Мы недавно с коллегой делали сайд проект, который полностью построили на концепции частных провайдерах. Если здесь в примерах мы код просто выносили в соседний файл «для удобства», то там как раз мы специально заделали папочку «providers» в основе проекта и добавляли туда различные провайдеры, которые позже сплетались в цепочки друг из друга. Над проектом работаем по 10-20 часов в неделю в течение нескольких месяцев, пока полет отличный — все сущности ничего не знают о существовании друг друга, весь полет данных идет через различные преобразования в провайдерах.

По поводу типизации: да, ошибки теоретически могут быть и TS никак не спасет, так уж устроен DI
А не будет ли проблем с отловом ошибок если прописали не тот интерфейс в фабрике или месте инжектирования

Будет, и еще какая, но это скорее проблема @Inject(), который не может проверить типы предоставленного значения и того что нужно.


const TT = new InjectionToken<number>("ddd");

@Component({
  providers: [
    {
      provide: TT,
      useValue: {}
    }
  ]
})
export class AppComponent {
  constructor(@Inject(TT) t: string) {
    console.log(t); 
  }
}

Как видим должен быть number, предоставили {}, а сам компонент ждет string — и ни одной ошибки. Поэтому @Inject() скорее антипатерн и вместо него почти всегда лучше использовать абстракный класс.


1) инжектируя работу роут как параметр ...

Для случая описанного в статье так-то есть https://angular.io/api/router/Resolve, который отложит рендеринг компонента до момента резолва всех резолверов (= пользователь не будет смотреть в пустую страницу) тем самым позволит выбросить *ngIf + можно вывести ошибку и опционально перенаправить пользователя на другую страницу если резолвинг провалился (= сервер вернул 404 например)


Правда с типами тут тоже не все идеально ибо data: Observable<{[name: string]: any;}>.

Мы в подобных кейсах используем резолверы. Однако такой подход тоже интересен. Статья хорошая, еще можно было бы добавить подход с резолверами о осветить плюсы и минусы частных провайдеров и резолверов. Или это уже тема для отдельной статьи? ;-)
Зарегистрируйтесь на Хабре, чтобы оставить комментарий