Руководство о том, как использовать Subject RxJS и BehaviourSubject RxJS для связи между компонентами Angular.
В этой статье я буду использовать RxJS, чтобы показать как компоненты взаимодействуют, когда они не знают друг друга или не имеют общих родительских / дочерних отношений.
Содержание:
- Проблема
- Способ 1: Транспорт событий
- Способ 2: Сервис-наблюдатель
- Применение
Проблема
Во многих фрэймворках, включая Angular, всегда возникает проблема взаимодействия компонентов, когда мы разделяем приложение на множество маленьких UI компонентов и привязываемся к родительскому элементу родительского элемента, чтобы слушать события.
В Angular мы используем Output() и Input(). В стандартных случаях этого достаточно, но когда нужно связать входящие данные и исходящие события с родительским компонентом, управление этим всем превращается в кошмар.
Нужно добавить кучу Input() и Output() ко многим уровням компонента – это требует больших усилий, рискованно и не всегда работает.
Одно из решений – использовать мэнеджер состояния – такой как Redux, NGRX или NGXS, чтобы помочь несвязанным компонентам обмениваться данными.
В этой статье я представлю два дополнительных способа для решения этой проблемы, не требующих использования дополнительных библиотек.
- Транспорт событий с использованием Subject.
- Сервис-наблюдатель с использованием Behavior Subject.
Чтобы продемонстрировать эти решения, я создам пример, в котором пользователь может щелкнуть элемент списка статей и отобразить подробности в другом компоненте.
Способ 1: Транспорт событий
Концепция очень проста. Вы создаёте сервис, события которого будут доступны везде.
Сервис распространяет события, а подписчики могут выполнить функцию обратного вызова, когда событие произошло. В этой статье я создам Транспорт событий с помощью RsJS Subject.

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

Этот код означает, что мы отправляем событие SelectArticleDetail вместе с информацией о статье.

Слушатель прослушает SelectArticleDetail и выполнит обратный вызов, передаст данные статьи в локальную переменную и отобразит их в пользовательском интерфейсе.
Способ 2: Сервис-наблюдатель
Идея в том, чтобы просто создать способ для передачи данных изнутри. То есть каждый раз при изменении какого-то значения наблюдатель узнает об этом и выполняет функцию обратного вызова.

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

Теперь, в компоненте детали, мы подпишемся на обновление хранилища, чтобы получить новое значение.
Применение
Я применял эти подходы во многих проектах. Вот несколько примеров, где это очень уместно:
- Транспорт событий: Я хочу использовать одно и то же модальное окно, чтобы показывать пользователю информацию о состоянии приложения каждый раз, когда он нажимает на кнопку
- Транспорт событий: Если используется моно-репозиторий с несколькими фрэймворками, удобно использовать этот подход для обмена событиями между фрэймворками или распространить событие от Angular к нативному JavaScript
- Транспорт событий и Сервис-наблюдатель для вложенных компонентов: сложно, используя Input() и Output() связать входящие/исходящие данные и события UI компонента D с UI компонентом B, c UI компонентом С и c родительским компонентом А при взаимодействии с API
Если у вас есть больше примеров, буду рад узнать о них. Учиться у других — отличный способ совершенствоваться.
Подводя итог
Эта статья о двух способах взаимодействия между двумя или более несвязанными компонентами.
Мы используем Сервис-наблюдатель, чтобы подписаться на данные для простых случаев, и используем Транспорт событий, чтобы отправлять разные события разным слушателям.
Надеюсь, статья была полезна! Подписывайтесь на меня в Medium и Twitter. Не стесняйтесь комментировать и задавать вопросы. Буду рад помочь!
Комментарии (6)
xenikopa
11.10.2019 20:21Хорошая статья, спасибо! Особенно интересно теперь применить первый способ по транспорту событий :)
Вопрос: В первом способе (Транспорт событий) — закрытие подписок идет черезunsubscribe
вngOnDestroy
? Или можно добавить параметр для оператораtakeUntil
, чтобы закрыть поток? Что вы делайте/думайте по этому поводу?
Немного из предложений (имхо):
- В способе 2 «Сервис-наблюдатель» хотелось бы больше примеров. Например, как по клику вызвать `addToInventory` и как использовать подписку на `inventoryChanged$`.
- Для сервисов можно использовать абстрактные классы, чтобы ограничить область видимости и выдать конкретный API работы с сервисом для разработчика. Можно использовать его через `providers` в нужном модуле. Например:
providers: [{provide: Service, useClass: IService }]
- Возьму смелость позанудничать: во втором примере круто было бы расположить `public` / `private` переменные последовательно :)
KiraJS Автор
11.10.2019 20:36/* Обычно так */ protected destroy: Subject<void> = new Subject(); ngOnDestroy() { this.destroy.next(); this.destroy.complete(); } /* И в подпичике разруливаю */ .pipe(takeUntil(this.destroy))
Что касается предложений, думаю автор статьи будет вам очень признателен, если вы их опубликуете в комментарии к его статье.
kubk
13.10.2019 06:09В оригинале есть пример как подписываться на inventoryChanged$:
«Now, in the detail component, we will subscribe to the inventory to get new value.». Там используется ручная подписка вместо async pipe, а отписки вообще нет. По поводу Транспорта событий хотел бы предостеречь — в примере из статьи нет типизации, при опечатке в названии события у вас просто ничего не произойдёт, так как оператор filter будет отсекать все события из-за несовпадения по свойству name. Пример типизации похожего кода: github.com/andywer/typed-emitter/blob/master/index.d.ts
Savva_Tobolsk
13.10.2019 19:54А что нам мешает решать все эти же проблемы с помощью удобных сервисов?
zhaparoff
Подход, конечно, имеет право на жизнь, особенно в случае, когда у тебя мегабайты стейта и не хочется напрягать память постоянными выделениями/освобождениями, что неизбежно при использовании иммутабельных хранилищ.
Однако, основной вопрос в масштабировании этой штуки… Что делать если у тебя 150 компонентов и 50 транспортов между ними? Как управлять и поддерживать такого макаронного монстра? Видимо, нужен еще какой-то фреймворк, который позволит легко настраивать и оркестрировать всю эту вакханалию — не знаю, существуют ли такие…
KiraJS Автор