Когда я должен хранить Subscription
экземпляры и вызывать их unsubscribe()
в течение жизненного цикла NgOnDestroy, а когда я могу просто игнорировать их?
Сохранение всех подписок вносит много ошибок в код компонента.
HTTP Client Guide игнорирует такие подписки:
getHeroes() {
this.heroService.getHeroes()
.subscribe(
heroes => this.heroes = heroes,
error => this.errorMessage = <any>error);
}
В то же время в Route & Navigation Guide говорится, что:
В конце концов мы перейдем куда-нибудь еще. Маршрутизатор удалит этот компонент из DOM и уничтожит его. Мы должны убраться за собой, прежде чем это произойдет. В частности, мы должны отписаться, прежде чем Angular уничтожит компонент. Невыполнение этого требования может привести к утечке памяти.
Отписываемся от нашего
Observable
вngOnDestroy
методе.
private sub: any;
ngOnInit() {
this.sub = this.route.params.subscribe(params => {
let id = +params['id']; // (+) converts string 'id' to a number
this.service.getHero(id).then(hero => this.hero = hero);
});
}
ngOnDestroy() {
this.sub.unsubscribe();
}
angular
rxjs
observable
subscription
angular-component-life-cycle
Сергей Тихон
источник
источник
Subscription
чтоhttp-requests
можно игнорировать, так как они звонят толькоonNext
один раз, а затем они звонятonComplete
.Router
Вместо вызововonNext
несколько раз и никогда не может назватьonComplete
(не уверен , что ...). То же самое касаетсяObservable
s отEvent
s. Так что, думаю, так и должно бытьunsubscribed
.subscribe
себе, который потенциально выделяет ресурсы в восходящем направлении.Ответы:
--- Редактировать 4 - Дополнительные ресурсы (2018/09/01)
В недавнем эпизоде приключений в Angular Бен Леш и Уорд Белл обсуждают вопросы о том, как и когда отписаться в компоненте. Обсуждение начинается примерно в 1:05:30.
Уорд упоминает
right now there's an awful takeUntil dance that takes a lot of machinery
и Ша Резник упоминаетAngular handles some of the subscriptions like http and routing
.В ответ Бен упоминает, что сейчас идут обсуждения, позволяющие Observables подключаться к событиям жизненного цикла компонента Angular, а Ward предлагает Observable событий жизненного цикла, на которые компонент может подписаться, чтобы узнать, когда завершать Observables, поддерживаемые как внутреннее состояние компонента.
Тем не менее, сейчас нам в основном нужны решения, так что вот некоторые другие ресурсы.
Рекомендация по
takeUntil()
шаблону от члена основной команды RxJ Николаса Джеймисона и правило tslint, чтобы помочь обеспечить его соблюдение. https://ncjamieson.com/avoiding-takeuntil-leaks/Облегченный пакет npm, который предоставляет оператор Observable, который принимает экземпляр компонента (
this
) в качестве параметра и автоматически отписывается во времяngOnDestroy
. https://github.com/NetanelBasal/ngx-take-until-destroyЕще один вариант вышеупомянутого с немного лучшей эргономикой, если вы не делаете сборки AOT (но мы все должны делать AOT сейчас). https://github.com/smnbbrv/ngx-rx-collector
Таможенная директива
*ngSubscribe
которая работает как асинхронный канал, но создает встроенный вид в вашем шаблоне, чтобы вы могли ссылаться на значение «развернутый» во всем шаблоне. https://netbasal.com/diy-subscription-handling-directive-in-angular-c8f6e762697fВ комментарии к блогу Николаса я упоминаю, что чрезмерное использование
takeUntil()
может быть признаком того, что ваш компонент пытается сделать слишком много, и что следует учитывать разделение существующих компонентов на компоненты Feature и Presentational . Вы можете тогда| async
заметить Observable из компонента Feature в компонентInput
Presentational, что означает, что подписки нигде не нужны. Подробнее об этом подходе читайте здесь--- Редактировать 3 - «Официальное» решение (2017/04/09)
Я говорил с Уордом Беллом об этом вопросе в NGConf (я даже показал ему этот ответ, который, по его словам, был правильным), но он сказал мне, что команда разработчиков документации для Angular нашла решение этого вопроса, которое не было опубликовано (хотя они работают над его утверждением). ). Он также сказал мне, что я могу обновить свой SO-ответ следующей официальной рекомендацией.
Решение, которое мы все должны использовать в будущем, заключается в добавлении
private ngUnsubscribe = new Subject();
поля ко всем компонентам, к которым.subscribe()
обращаютсяObservable
s в своем коде класса.Затем мы позвоним
this.ngUnsubscribe.next(); this.ngUnsubscribe.complete();
в нашngOnDestroy()
методы.Секретный соус (как уже отмечалось @metamaker ) - звонить
takeUntil(this.ngUnsubscribe)
перед каждым из наших.subscribe()
вызовов, что гарантирует очистку всех подписок при уничтожении компонента.Пример:
Примечание: важно добавить
takeUntil
оператор как последний, чтобы предотвратить утечки с промежуточными наблюдаемыми в цепочке операторов.--- Изменить 2 (2016/12/28)
Источник 5
В учебнике Angular глава Routing теперь заявляет следующее: «Маршрутизатор управляет наблюдаемыми объектами, которые он предоставляет, и локализует подписки. Подписки очищаются при уничтожении компонента, защищая от утечек памяти, поэтому нам не нужно отписываться от параметры маршрута наблюдаемые. " - Марк Райкок
Вот обсуждение вопросов Github для Angular docs, касающихся Router Observables, где Уорд Белл упоминает, что прояснение всего этого находится в работе.
--- Редактировать 1
Источник 4
В этом видео от NgEurope Роб Вормальд также говорит, что вам не нужно отписываться от Router Observables. Он также упоминает
http
службу иActivatedRoute.params
в этом видео с ноября 2016 года .--- Оригинальный ответ
TLDR:
Для этого вопроса существует (2) вида
Observables
- конечное значение и бесконечное значение.http
Observables
производят конечные (1) ценности и что - то вроде DOMevent listener
Observables
производят бесконечные значения.Если вы вручную звоните
subscribe
(не используя асинхронный канал), тоunsubscribe
из бесконечногоObservables
.Не беспокойтесь о конечных ,
RxJs
позаботятся о них.Источник 1
Я разыскал ответ Роба Вормальда в Angular's Gitter здесь .
Он заявляет (я реорганизован для ясности и акцента мое)
Также он упоминает в этом видео на YouTube об Observables, которые
they clean up after themselves
... в контексте Observables, которыеcomplete
(например, Promises, которые всегда выполняются, потому что они всегда производят 1 значение и заканчиваются - мы никогда не беспокоились о том, чтобы отписаться от Promises, чтобы убедиться, что они очищаютxhr
событие слушатели, верно?).Источник 2
Также в путеводителе по Rangle Angular 2 написано
Когда делает фразу
our Observable has a longer lifespan than our subscription
?Он применяется, когда подписка создается внутри компонента, который уничтожается до (или незадолго до)
Observable
завершения.Я читаю это как значение, если мы подписываемся на
http
запрос или наблюдаемое, которое испускает 10 значений, и наш компонент уничтожается до того, как этотhttp
запрос вернется или 10 значений будут отправлены, мы все еще в порядке!Когда запрос вернется или, наконец,
Observable
будет получено 10-е значение, все будет завершено, и все ресурсы будут очищены.Источник 3
Если мы посмотрим на этот пример из того же руководства по Rangle, то увидим, что для
Subscription
toroute.params
требуется значение,unsubscribe()
потому что мы не знаем, когда ониparams
перестанут меняться (испуская новые значения).Компонент может быть уничтожен путем перемещения в этом случае, и в этом случае параметры маршрута, вероятно, все еще будут изменяться (они могут технически измениться до конца приложения), а ресурсы, выделенные в подписке, будут по-прежнему выделяться, поскольку не было
completion
.источник
complete()
сам по себе не может очистить подписки. Как бы то ни было, вызов,next()
а затемcomplete()
и выполнение, я полагаю,takeUntil()
прекращается только при получении значения, а не при завершении последовательности.Subject
внутри компонента и переключением его с помощьюngIf
триггераngOnInit
иngOnDestroy
показывает, что субъект и его подписки никогда не завершатся или не будут уничтожены (подключен операторfinally
-подписчик к подписке). Я должен позвонитьSubject.complete()
вngOnDestroy
, так подписки можно убирать за собой.takeUnitl
подход, нам никогда не придется вручную отписываться от каких-либо наблюдаемых? Это тот случай? Кроме того, почему мы должны вызватьnext()
вngOnDestroy
, почему бы просто не позвонитьcomplete()
?Вам не нужно иметь кучу подписок и отписаться вручную. Используйте Subject и takeUntil combo для обработки подписок как босс:
Альтернативный подход , который был предложен @acumartini в комментариях , использует takeWhile вместо takeUntil . Вы можете предпочесть это, но имейте в виду, что таким образом выполнение Observable не будет отменено для ngDestroy вашего компонента (например, когда вы выполняете трудоемкие вычисления или ждете данных с сервера). Метод, основанный на takeUntil , не имеет этого недостатка и приводит к немедленной отмене запроса. Спасибо @AlexChe за подробное объяснение в комментариях .
Итак, вот код:
источник
takeUntil
иtakeWhile
. Первый отписывается от источника, наблюдаемого сразу после запуска, в то время как второй отписывается только после того, как источник получает наблюдаемое значение. Если создание значения с помощью источника, наблюдаемого является операцией, потребляющей ресурсы, выбор между ними может выходить за рамки предпочтений стиля. Увидеть планкtakeUntil
противtakeWhile
, но не для нашего конкретного случая. Когда нам нужно отписаться от подписчиков на уничтожение компонентов , мы просто проверяем логическое значение, как() => alive
вtakeWhile
, так что любые операции, занимающие время / память, не используются, и различие в значительной степени связано со стилем (ofc, для этого конкретного случая).Observable
, которая внутренне добывает некоторую криптовалюту и запускаетnext
событие для каждой добытой монеты, а добыча одной такой монеты занимает день. С помощьюtakeUntil
мы отписываемся от источника майнингаObservable
сразу, как толькоngOnDestroy
вызывается во время уничтожения нашего компонента. Таким образом,Observable
функция майнинга может немедленно отменить свою работу во время этого процесса.takeWhile
, вngOnDestory
мы просто устанавливаем логическую переменную. НоObservable
функция майнинга может работать до одного дня, и только тогда во времяnext
вызова она поймет, что активных подписок нет, и ее нужно отменить.Класс подписки имеет интересную особенность:
Вы можете создать совокупный объект подписки, который группирует все ваши подписки. Это можно сделать, создав пустую подписку и добавив к ней подписки, используя ее
add()
метод. Когда ваш компонент уничтожен, вам нужно только отменить подписку на совокупную подписку.источник
takeUntil
подхода по сравнению с этим подходом сбора подписок и вызововunsubscribe
. (Этот подход кажется мне чище.)this.subscriptions
nullsub = subsciption.add(..).add(..)
потому что во многих случаях это приводит к неожиданным результатам github.com/ReactiveX/rxjs/issues/2769#issuecomment-345636477Некоторые из лучших практик относительно наблюдаемых отписок внутри угловых компонентов:
Цитата из
Routing & Navigation
И в ответ на следующие ссылки:
http
наблюдаемомЯ собрал некоторые из лучших практик в отношении аннулируемых подписок внутри компонентов Angular, чтобы поделиться ими с вами:
http
наблюдаемая отмена подписки является условной, и мы должны учитывать последствия «обратного вызова подписки», запускаемого после уничтожения компонента в каждом конкретном случае. Мы знаем, что угол отписывается и очищаетhttp
саму наблюдаемую (1) , (2) . Хотя это верно с точки зрения ресурсов, это только половина истории. Допустим, мы говорим о прямом звонкеhttp
из компонента, аhttp
ответ занял больше времени, чем нужно, поэтому пользователь закрыл компонент.subscribe()
Обработчик будет по-прежнему вызываться, даже если компонент закрыт и уничтожен. Это может иметь нежелательные побочные эффекты, а в худших сценариях состояние приложения нарушается. Это также может вызвать исключения, если код в обратном вызове пытается вызвать что-то, что только что было удалено. Однако в то же время иногда они желательны. Например, предположим, что вы создаете почтовый клиент и запускаете звуковой сигнал, когда электронная почта завершает отправку - хорошо, что вы все равно хотите, чтобы это произошло, даже если компонент закрыт ( 8 ).AsyncPipe
как можно больше, потому что он автоматически отписывается от наблюдаемого при уничтожении компонента.ActivatedRoute
наблюдаемых, например,route.params
если они подписаны внутри вложенного (добавлено в tpl с помощью селектора компонента) или динамического компонента, так как они могут быть подписаны много раз, пока существует родительский / хост-компонент. Нет необходимости отписываться от них в других сценариях, как указано в приведенной выше цитате изRouting & Navigation
документации.Примечание. Что касается сервисов с определенной областью, то есть поставщиков компонентов, они уничтожаются при уничтожении компонента. В этом случае, если мы подписываемся на какой-либо наблюдаемый внутри этого провайдера, мы должны рассмотреть возможность отказа от него, используя
OnDestroy
ловушку жизненного цикла, которая будет вызываться при разрушении службы, согласно документам.takeUntil
(3) или использовать этотnpm
пакет, упомянутый в (4) Самый простой способ отписаться от Observables в Angular .FormGroup
наблюдаемых какform.valueChanges
иform.statusChanges
Renderer2
службы, какrenderer2.listen
HostListener
как angular заботится об удалении слушателей событий, если это необходимо, и предотвращает любую потенциальную утечку памяти из-за привязок событий.Хороший заключительный совет : если вы не знаете, автоматически ли отписывается / завершается заметка или нет, добавьте
complete
обратный вызовsubscribe(...)
и проверьте, вызывается ли он при уничтожении компонента.источник
ngOnDestroy
вызывают, когда служба предоставляется на уровне, отличном от корневого, например, предоставляется явно в компоненте, который впоследствии удаляется. В этих случаях вам следует отписаться от сервисов внутреннихngOnDestroy
, которая всегда вызывается при уничтожении сервисов github.com/angular/angular/commit/…Feel free to unsubscribe anyway. It is harmless and never a bad practice.
и что касается вашего вопроса, это зависит. Если дочерний компонент запускается несколько раз (например, добавляется внутриngIf
или загружается динамически), необходимо отменить подписку, чтобы избежать добавления нескольких подписок к одному и тому же наблюдателю. В противном случае нет необходимости. Но я предпочитаю отписываться внутри дочернего компонента, так как это делает его более пригодным для повторного использования и изолированным от того, как его можно использовать.Это зависит. Если при вызове
someObservable.subscribe()
вы начинаете задерживать какой-то ресурс, который должен быть освобожден вручную, когда жизненный цикл вашего компонента закончен, вам следует вызватьtheSubscription.unsubscribe()
чтобы предотвратить утечку памяти.Давайте внимательнее посмотрим на ваши примеры:
getHero()
возвращает результатhttp.get()
. Если вы посмотрите на исходный код angular 2 ,http.get()
создайте два прослушивателя событий:и, позвонив
unsubscribe()
, вы можете отменить запрос, а также слушателей:Обратите внимание, что это
_xhr
зависит от платформы, но я думаю, можно с уверенностью предположить, что этоXMLHttpRequest()
в вашем случае.Обычно этого достаточно для подтверждения ручного
unsubscribe()
вызова. Но согласно этой спецификации WHATWG ,XMLHttpRequest()
объект подлежит сборке мусора, как только он «сделан», даже если к нему подключены прослушиватели событий. Поэтому, я думаю, именно поэтому в angular 2 официальное руководство опускаетсяunsubscribe()
и позволяет GC убирать слушателей.Что касается вашего второго примера, это зависит от реализации
params
. На сегодняшний день в угловом официальном руководстве больше не отображается отписка отparams
. Я снова заглянул в src и обнаружил, чтоparams
это просто BehaviorSubject . Поскольку ни прослушиватели событий, ни таймеры не использовались, а глобальные переменные не создавались, их можно с уверенностью опуститьunsubscribe()
.Суть в том, что вы всегда вызываете
unsubscribe()
защиту от утечки памяти, если только вы не уверены, что выполнение наблюдаемой не создает глобальные переменные, не добавляет прослушиватели событий, не устанавливает таймеры и не делает ничего другого, что приводит к утечкам памяти. ,Если есть сомнения, посмотрите на реализацию этого наблюдаемого. Если наблюдаемая записала в нее некоторую логику очистки
unsubscribe()
, которая обычно является функцией, возвращаемой конструктором, то у вас есть веская причина серьезно подумать о вызовеunsubscribe()
.источник
Официальная документация Angular 2 содержит объяснение того, когда отписаться и когда ее можно безопасно игнорировать. Посмотрите на эту ссылку:
https://angular.io/docs/ts/latest/cookbook/component-communication.html#!#bidirectional-service
Найдите абзац с заголовком « Родитель и дети общаются через службу», а затем синее поле:
Я надеюсь, это поможет вам.
источник
Основано на: Использование наследования классов для привязки к жизненному циклу компонента Angular 2
Еще один общий подход:
И использовать:
источник
this.componentDestroyed$.next()
вызов , как общепринятое решение Шона выше ...Официальный ответ Edit # 3 (и варианты) работает хорошо, но меня привлекает «мутность» бизнес-логики вокруг наблюдаемой подписки.
Вот еще один подход с использованием оберток.
Файл subscribeAndGuard.ts используется для создания нового расширения Observable для переноса
.subscribe()
и внутри него для переносаngOnDestroy()
.Использование такое же, как
.subscribe()
, за исключением дополнительного первого параметра, ссылающегося на компонент.Вот компонент с двумя подписками, одна с оберткой и одна без. Единственное предостережение в том, что он должен реализовывать OnDestroy (с пустым телом, если необходимо), в противном случае Angular не знает, чтобы вызывать упакованную версию.
Демоверсия здесь
Дополнительное примечание: Re Edit 3 - «Официальное» решение, это можно упростить, используя takeWhile () вместо takeUntil () перед подпиской, и просто логическое значение, а не другое Observable в ngOnDestroy.
источник
Поскольку решение seangwright (Edit 3) кажется очень полезным, я также счел затруднительным упаковать эту функцию в базовый компонент, и дал подсказку другим партнерам по команде проекта не забывать вызывать super () для ngOnDestroy, чтобы активировать эту функцию.
Этот ответ предоставляет способ освободиться от супер-вызова и сделать «componentDestroyed $» ядром базового компонента.
И тогда вы можете свободно использовать эту функцию, например:
источник
После ответа @seangwright я написал абстрактный класс, который обрабатывает «бесконечные» подписки наблюдаемых в компонентах:
Чтобы использовать его, просто расширьте его в своем угловом компоненте и вызовите
subscribe()
метод следующим образом:Он также принимает ошибку и завершает обратные вызовы, как обычно, объект-наблюдатель или вообще не обратные вызовы. Не забудьте вызвать,
super.ngOnDestroy()
если вы также реализуете этот метод в дочернем компоненте.Найдите здесь дополнительную ссылку Бена Леша: RxJS: Не отписывайтесь .
источник
Я попробовал решение Seangwright (Edit 3)
Это не работает для Observable, созданного таймером или интервалом.
Тем не менее, я получил это с помощью другого подхода:
источник
Мне нравятся последние два ответа, но у меня возникла проблема, если подкласс, указанный
"this"
вngOnDestroy
.Я изменил это, чтобы быть этим, и похоже, что это решило эту проблему.
источник
this.ngOnDestroy = () => { f.bind(this)(); this.componentDestroyed$.complete(); };
В случае необходимости отписки можно использовать следующий оператор для метода наблюдаемой трубы
это можно использовать так:
Оператор переносит метод компонента ngOnDestroy.
Важно: оператор должен быть последним в наблюдаемой трубе.
источник
Обычно вам нужно отписаться, когда компоненты будут уничтожены, но Angular будет обрабатывать их все больше и больше, например, в новой минорной версии Angular4 у них есть этот раздел для отмены подписки:
Также приведенный ниже пример является хорошим примером из Angular для создания компонента и его уничтожения после, посмотрите, как компонент реализует OnDestroy. Если вам нужен onInit, вы также можете реализовать его в своем компоненте, например, как
OnInit, OnDestroy
источник
Еще одно короткое дополнение к вышеупомянутым ситуациям:
источник
в применении SPA в ngOnDestroy функции (угловой) Жизненным Циклом Для каждой подписки вы должны отказаться его. преимущество => чтобы государство не стало слишком тяжелым.
например: в компоненте 1:
в сервисе:
в компоненте 2:
источник
Для обработки подписки я использую класс «Unsubscriber».
Вот класс Unsubscriber.
И Вы можете использовать этот класс в любом компоненте / Сервис / Эффект и т. Д.
Пример:
источник
Вы можете использовать последний
Subscription
класс, чтобы отказаться от подписки на Observable с не таким грязным кодом.Мы можем сделать это,
normal variable
но это будетoverride the last subscription
при каждой новой подписке, поэтому избегайте этого, и этот подход очень полезен, когда вы имеете дело с большим количеством Obseravables и типа Obeservables, таких какBehavoiurSubject
иSubject
Подписка
Вы можете использовать это двумя способами,
Вы можете напрямую передать подписку на массив подписок
используя
add()
вSubscription
А
Subscription
может проводить дочерние подписки и безопасно отписываться от них всех. Этот метод обрабатывает возможные ошибки (например, если какие-либо дочерние подписки нулевые).Надеюсь это поможет.. :)
источник
Пакет SubSink, простое и последовательное решение для отказа от подписки
Поскольку никто другой не упомянул об этом, я хочу порекомендовать пакет Subsink, созданный Ward Bell: https://github.com/wardbell/subsink#readme .
Я использовал его в проекте, где мы работали с несколькими разработчиками. Очень помогает иметь последовательный способ, который работает в любой ситуации.
источник
Для наблюдаемых, которые завершаются непосредственно после выдачи результата, например
AsyncSubject
или, например, наблюдаемых из http-запросов, вам не нужно отписываться. Не больно вызыватьunsubscribe()
их, но если наблюдаемыйclosed
метод - отписаться, он просто ничего не сделает :Если у вас есть долгоживущие наблюдаемые, которые выдают несколько значений с течением времени (например, a
BehaviorSubject
или aReplaySubject
), вам нужно отписаться, чтобы предотвратить утечки памяти.Вы можете легко создать наблюдаемую, которая завершается непосредственно после получения результата от таких долгоживущих наблюдаемых, используя оператор канала. В некоторых ответах здесь упоминается
take(1)
труба. Но я предпочитаю вfirst()
трубу . Разница вtake(1)
том, что это будет:Еще одним преимуществом первого канала является то, что вы можете передать предикат, который поможет вам вернуть первое значение, которое удовлетворяет определенным критериям:
Первое завершится непосредственно после выдачи первого значения (или при передаче аргумента функции первого значения, которое удовлетворяет вашему предикату), поэтому отпадает необходимость отписываться.
Иногда вы не уверены, наблюдаете ли вы долгоживущие или нет. Я не говорю, что это хорошая практика, но вы всегда можете добавить
first
канал, чтобы убедиться, что вам не нужно будет отписываться вручную. Добавление дополнительнойfirst
трубы к наблюдаемой, которая будет излучать только одно значение, не повредит.В процессе разработки вы можете использовать в
single
трубу , которая будет выполнена , если исходные наблюдаемыми испускает несколько событий. Это может помочь вам изучить тип наблюдаемого и необходимость отписаться от него или нет.first
Иsingle
кажется очень похожи, обе труба может взять дополнительный предикат , но различия являются важными и хорошо представлены в этом StackOverflow ответа здесь :Обратите внимание, что я старался быть максимально точным и полным в своем ответе со ссылками на официальную документацию, но, пожалуйста, прокомментируйте, если что-то важное отсутствует ...
источник
--- Обновление Angular 9 и Rxjs 6 Solution
unsubscribe
наngDestroy
жизненном цикле углового компонентаtakeUntil
в RxjsngOnInit
, это происходит только один раз, когда компонент init.У нас тоже есть
async
труба. Но это использование в шаблоне (не в угловом компоненте).источник