Как подписываться на изменения в BehaviorSubject и обрабатывать их
Что проверяют
Знаете ли вы, чем BehaviorSubject отличается от обычного Subject, и умеете ли подписаться так, чтобы не оставить утечку. Во фронтенде вопрос почти всегда про Angular: сервис хранит состояние, компоненты на него подписываются. Следом спрашивают, как отписываться и зачем нужен asObservable().
Как отвечать
Суть: BehaviorSubject требует начальное значение, хранит последнее и сразу отдаёт его каждому новому подписчику. Текущее значение читается синхронно через getValue() или свойство value, новое кладётся через next().
Каркас правильного использования:
- инкапсуляция: в сервисе приватный
BehaviorSubject, наружу толькоasObservable(). Менять состояние могут лишь методы сервиса; - подписка в шаблоне:
asyncpipe сам подписывается и отписывается при уничтожении компонента, это основной способ; - подписка в коде:
pipe(takeUntilDestroyed())из@angular/core/rxjs-interopвместо ручногоSubscriptionиngOnDestroy. Вне контекста внедрения ему нужно передатьDestroyRef; - обработка:
mapдля выборки нужного куска,distinctUntilChanged, чтобы не реагировать на то же значение,debounceTimeдля частых обновлений,switchMapдля запроса по новому значению с отменой старого; - сигналы:
toSignal()превращает поток в сигнал, и шаблон читает его безasync. Для простого локального состояния сигналы в современных версиях Angular уже заменяютBehaviorSubject.
Полезно сравнить с ReplaySubject(1): он тоже отдаёт последнее значение, но не требует начального и молчит, пока значения не было.
Где спотыкаются
Отдают сам BehaviorSubject наружу, и любой компонент может вызвать next(). Состояние начинают менять из десяти мест, и найти источник бага трудно.
Подписываются в ngOnInit без отписки. С HTTP-запросом это сходит с рук, потому что поток завершается, а BehaviorSubject не завершается никогда.
Мутируют объект и вызывают next() с той же ссылкой. distinctUntilChanged и любые сравнения по ссылке изменения не видят, нужна новая копия объекта.