Angular/RxJs Когда я должен отказаться от подписки на `Subscription`

Когда следует хранить экземпляры Subscription и вызывать unsubscribe unsubscribe() во время жизненного цикла NgOnDestroy, и когда я могу просто игнорировать их?

Сохранение всех подписей вводит много беспорядка в код компонента.

Руководство по HTTP-клиенту игнорирует подписки следующим образом:

getHeroes() {
  this.heroService.getHeroes()
                   .subscribe(
                     heroes => this.heroes = heroes,
                     error =>  this.errorMessage = <any>error);
}

В то же время Руководство по маршрутизации и навигации говорит, что:

В конце концов, мы будем перемещаться в другом месте. Маршрутизатор удалит этот компонент из 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();
}

Ответ 1

--- Редактировать 4 - Дополнительные ресурсы (2018/09/01)

В недавнем эпизоде Приключения в Угловом Бене Лэше и Уорде Белле обсуждаются вопросы о том, как/когда отказаться от подписки в компоненте. Обсуждение начинается примерно в 1:05:30.

Уорд упоминает, что right now there an awful takeUntil dance that takes a lot of machinery и Шай Резник упоминает, что Angular handles some of the subscriptions like http and routing.

В ответ Бэн упоминает, что сейчас есть обсуждения, чтобы позволить Observables подключаться к событиям жизненного цикла углового компонента, и Уорд предлагает Наблюдение за событиями жизненного цикла, которые компонент мог бы подписаться, как способ узнать, когда завершить Observables, поддерживаемое как внутреннее состояние компонента.

Тем не менее, мы в основном нуждаемся в решениях, поэтому здесь есть некоторые другие ресурсы.

  1. Рекомендация для takeUntil() от основного члена команды RxJs Николаса Джеймисона и правила tslint, чтобы помочь обеспечить его выполнение. https://blog.angularindepth.com/rxjs-avoiding-takeuntil-leaks-fb5182d047ef

  2. Легкий пакет npm, который предоставляет оператор Observable, который принимает экземпляр компонента (this) в качестве параметра и автоматически ngOnDestroy подписку во время ngOnDestroy. https://github.com/NetanelBasal/ngx-take-until-destroy

  3. Другой вариант выше, с немного лучшей эргономикой, если вы не делаете сборки AOT (но мы все должны делать AOT сейчас). https://github.com/smnbbrv/ngx-rx-collector

  4. Пользовательская директива *ngSubscribe которая работает как async pipe, но создает встроенный вид в вашем шаблоне, поэтому вы можете ссылаться на "развернутое" значение по всему шаблону. https://netbasal.com/diy-subscription-handling-directive-in-angular-c8f6e762697f

Я упомянул в комментарии к блогу Николаса, что чрезмерное использование takeUntil() может быть признаком того, что ваш компонент пытается сделать слишком много, и что нужно отделить ваши существующие компоненты от компонентов Feature и Presentational. Вы можете затем | async | async Наблюдаемое из компонента Feature во Input Презентационного компонента, что означает, что никакие подписки не нужны нигде. Подробнее об этом подходе читайте здесь.

--- Редактировать 3 - "Официальное" решение (2017/04/09)

Я говорил с Уордом Белл об этом вопросе в NGConf (я даже показал ему этот ответ, который, по его словам, был прав), но он сказал мне, что команда docs для Angular имела решение этого вопроса, который не опубликован (хотя они работают над тем, чтобы получить одобрение). Он также сказал мне, что смогу обновить свой SO-ответ с предстоящей официальной рекомендацией.

Решение, которое мы все должны использовать в будущем, - это добавить private ngUnsubscribe = new Subject(); поле для всех компонентов, которые имеют .subscribe() для Observable в своем коде кода.

Затем мы вызываем this.ngUnsubscribe.next(); this.ngUnsubscribe.complete(); this.ngUnsubscribe.next(); this.ngUnsubscribe.complete(); в наших ngOnDestroy().

Секретный соус (как уже заметил @metamaker) заключается в вызове takeUntil(this.ngUnsubscribe) перед каждым из наших .subscribe() которые гарантируют, что все подписки будут очищены, когда компонент будет уничтожен.

Пример:

import { Component, OnDestroy, OnInit } from '@angular/core';
// RxJs 6.x+ import paths
import { filter, startWith, takeUntil } from 'rxjs/operators';
import { Subject } from 'rxjs';
import { BookService } from '../books.service';

@Component({
    selector: 'app-books',
    templateUrl: './books.component.html'
})
export class BooksComponent implements OnDestroy, OnInit {
    private ngUnsubscribe = new Subject();

    constructor(private booksService: BookService) { }

    ngOnInit() {
        this.booksService.getBooks()
            .pipe(
               startWith([]),
               filter(books => books.length > 0),
               takeUntil(this.ngUnsubscribe)
            )
            .subscribe(books => console.log(books));

        this.booksService.getArchivedBooks()
            .pipe(takeUntil(this.ngUnsubscribe))
            .subscribe(archivedBooks => console.log(archivedBooks));
    }

    ngOnDestroy() {
        this.ngUnsubscribe.next();
        this.ngUnsubscribe.complete();
    }
}

Примечание. Важно добавить оператор takeUntil в качестве последнего для предотвращения утечек с промежуточными наблюдаемыми в цепочке операторов.

--- Редактировать 2 (2016/12/28)

Источник 5

В главе "Угловое" в главе "Маршрутизация" говорится следующее: "Маршрутизатор управляет наблюдаемыми, которые он предоставляет, и локализует подписки. Подписки очищаются, когда компонент уничтожается, защищая от утечек памяти, поэтому нам не нужно отписываться от параметры маршрута Observable. " - Марк Райкок

Здесь обсуждение вопросов Гитуба для Угловых документов, касающихся наблюдателей на маршрутизаторах, где Уорд Белл упоминает это разъяснение для всего этого, находится в работах.

--- Редактировать 1

Источник 4

В этом видео от NgEurope Роб Уормальд также говорит, что вам не нужно отказаться от подписки на Router Observables. Он также упоминает http сервис и ActivatedRoute.params в этом видео с ноября 2016 года.

--- Оригинальный ответ

TL;DR:

Для этого вопроса существуют (2) виды Observables - конечное значение и бесконечное значение.

http Observables производит конечные (1) значения и что-то вроде event listener DOM. Observables производят бесконечные значения.

Если вы вручную вызываете subscribe (не используя асинхронный канал), отмените unsubscribe бесконечные Observables.

Не беспокойтесь о конечных, RxJs позаботится о них.

Источник 1

Я отыскал ответ от Роба Вормальда в "Угловом Гиттере" здесь.

Он утверждает, что (я реорганизован для ясности, и акцент мой)

если его однозначная последовательность (например, HTTP-запрос), ручная очистка не нужна (при условии, что вы подписались в контроллере вручную)

я должен сказать "если его последовательность, которая завершается " (одна из последовательностей значений, a la http, одна)

если его бесконечная последовательность, вы должны отказаться от подписки, которая для вас делает асинхронный канал

Также он упоминает в этом видеоролике YouTube о Observables, что they clean up after themselves... в контексте Observables, которые complete (например, обещания, которые всегда завершаются, потому что они всегда производят 1 ценность и заканчиваются - мы никогда не беспокоимся о том, чтобы отказаться от подписки от Promises до убедитесь, что они очищают слушателей событий xhr, не так ли?).

Источник 2

Также в руководстве Rangle для Angular 2 он читает

В большинстве случаев нам не нужно будет явно ссылаться на метод отмены подписки, если мы не хотим отменить раннее, или наш Observable имеет более длительный срок службы, чем наша подписка. Поведение операторов Observable по умолчанию заключается в том, чтобы избавиться от подписки, как только публикуются сообщения.complete() или.error(). Имейте в виду, что RxJS был разработан для использования в режиме "огонь и забыть" большую часть времени.

Когда фраза " our Observable has a longer lifespan than our subscription?

Он применяется, когда подписка создается внутри компонента, который был уничтожен до (или не "длинный") до завершения Observable.

Я читал это как значение, если мы подписываемся на http запрос или наблюдаемый, который испускает 10 значений, и наш компонент уничтожается до того, как этот запрос http вернется или 10 значений были испущены, мы все еще в порядке!

Когда запрос вернется или 10-е значение будет окончательно выпущено, Observable завершит работу и все ресурсы будут очищены.

Источник 3

Если мы посмотрим на этот пример из того же руководства Rangle, мы увидим, что для Subscription to route.params требуется route.params unsubscribe() потому что мы не знаем, когда эти params перестанут меняться (выдавая новые значения).

Компонент может быть уничтожен путем навигации, в этом случае параметры маршрута, вероятно, будут по-прежнему меняться (они могут технически измениться до тех пор, пока приложение не закончится), а ресурсы, выделенные в подписке, все равно будут выделены, потому что не было completion.

Ответ 2

Вам не нужно иметь кучу подписок и отписаться вручную. Используйте Subject и takeUntil для обработки подписок, например, босса:

import { Subject } from "rxjs"
import { takeUntil } from "rxjs/operators"

@Component({
  moduleId: __moduleName,
  selector: "my-view",
  templateUrl: "../views/view-route.view.html"
})
export class ViewRouteComponent implements OnInit, OnDestroy {
  componentDestroyed$: Subject<boolean> = new Subject()

  constructor(private titleService: TitleService) {}

  ngOnInit() {
    this.titleService.emitter1$
      .pipe(takeUntil(this.componentDestroyed$))
      .subscribe((data: any) => { /* ... do something 1 */ })

    this.titleService.emitter2$
      .pipe(takeUntil(this.componentDestroyed$))
      .subscribe((data: any) => { /* ... do something 2 */ })

    //...

    this.titleService.emitterN$
      .pipe(takeUntil(this.componentDestroyed$))
      .subscribe((data: any) => { /* ... do something N */ })
  }

  ngOnDestroy() {
    this.componentDestroyed$.next(true)
    this.componentDestroyed$.complete()
  }
}

Альтернативный подход, предложенный @acumartini в комментариях, использует takeWhile вместо takeUntil. Вы можете предпочесть это, но имейте в виду, что таким образом выполнение Observable не будет отменено для ngDestroy вашего компонента (например, когда вы выполняете трудоемкие вычисления или ждете данных с сервера). Метод, основанный на takeUntil, не имеет этого недостатка и приводит к немедленной отмене запроса. Спасибо @AlexChe за подробное объяснение в комментариях.

Итак, вот код:

@Component({
  moduleId: __moduleName,
  selector: "my-view",
  templateUrl: "../views/view-route.view.html"
})
export class ViewRouteComponent implements OnInit, OnDestroy {
  alive: boolean = true

  constructor(private titleService: TitleService) {}

  ngOnInit() {
    this.titleService.emitter1$
      .pipe(takeWhile(() => this.alive))
      .subscribe((data: any) => { /* ... do something 1 */ })

    this.titleService.emitter2$
      .pipe(takeWhile(() => this.alive))
      .subscribe((data: any) => { /* ... do something 2 */ })

    // ...

    this.titleService.emitterN$
      .pipe(takeWhile(() => this.alive))
      .subscribe((data: any) => { /* ... do something N */ })
  }

  // Probably, this.alive = false MAY not be required here, because
  // if this.alive === undefined, takeWhile will stop. I
  // will check it as soon, as I have time.
  ngOnDestroy() {
    this.alive = false
  }
}

Ответ 3

Класс Subscription имеет интересную особенность:

Представляет одноразовый ресурс, такой как выполнение Observable. Подписка имеет один важный метод, отменить подписку, который не принимает аргументов и просто распоряжается ресурсом, хранящимся в подписке.
Кроме того, подписки могут быть сгруппированы вместе с помощью метода add(), который будет прикреплять дочернюю подписку к текущей подписке. Когда подписка будет отписана, все ее дети (и ее внуки) также будут отписаны.

Вы можете создать совокупный объект Subscription, который группирует все ваши подписки. Вы делаете это, создавая пустую подписку и добавляя подписки к ней с помощью метода add(). Когда ваш компонент уничтожен, вам нужно отказаться от подписки на общую подписку.

@Component({ ... })
export class SmartComponent implements OnInit, OnDestroy {
  private subscriptions = new Subscription();

  constructor(private heroService: HeroService) {
  }

  ngOnInit() {
    this.subscriptions.add(this.heroService.getHeroes().subscribe(heroes => this.heroes = heroes));
    this.subscriptions.add(/* another subscription */);
    this.subscriptions.add(/* and another subscription */);
    this.subscriptions.add(/* and so on */);
  }

  ngOnDestroy() {
    this.subscriptions.unsubscribe();
  }
}

Ответ 4

Некоторые из лучших практик относительно наблюдаемых отписок внутри угловых компонентов:

Цитата из Routing & Navigation

При подписке на наблюдаемое в компоненте вы почти всегда соглашаетесь отменить подписку, когда компонент уничтожен.

Есть несколько исключительных наблюдаемых, где это не обязательно. Наблюдаемые значения ActivatedRoute входят в число исключений.

ActivatedRoute и его наблюдаемые изолированы от самого маршрутизатора. Маршрутизатор уничтожает маршрутизируемый компонент, когда он больше не нужен, и внедренный ActivatedRoute умирает вместе с ним.

Не стесняйтесь отписаться в любом случае. Это безвредно и никогда не бывает плохой практикой.

И в ответ на следующие ссылки:

Я собрал некоторые из лучших практик в отношении аннулируемых подписок внутри компонентов Angular, чтобы поделиться ими с вами:

  • Наблюдаемая отмена подписки http является условной, и мы должны учитывать последствия "обратного вызова подписки", запускаемого после уничтожения компонента в каждом конкретном случае. Мы знаем, что Angular отписывается и очищает сам http наблюдаемый (1), (2). Хотя это верно с точки зрения ресурсов, это только половина истории. Допустим, мы говорим о прямом вызове http из компонента, и ответ http занял больше времени, чем необходимо, поэтому пользователь закрыл компонент. Обработчик subscribe() будет по-прежнему вызываться, даже если компонент закрыт и уничтожен. Это может иметь нежелательные побочные эффекты, а в худших сценариях состояние приложения нарушается. Это также может вызвать исключения, если код в обратном вызове пытается вызвать что-то, что только что было удалено. Однако в то же время иногда они желательны. Например, допустим, вы создаете почтовый клиент и запускаете звуковой сигнал, когда электронная почта завершает отправку - хорошо, что вы все равно хотите, чтобы это произошло, даже если компонент закрыт (8).
  • Не нужно отписываться от наблюдаемых или завершенных ошибок. Тем не менее, в этом нет никакого вреда (7).
  • AsyncPipe используйте AsyncPipe потому что он автоматически отписывается от наблюдаемого при уничтожении компонента.
  • route.params подписку в наблюдаемых route.params ActivatedRoute таких как route.params если они подписаны внутри вложенного (добавлено в tpl с помощью селектора компонента) или динамического компонента, так как они могут быть подписаны много раз, пока существует родительский/хост-компонент. Нет необходимости отписываться от них в других сценариях, как указано в приведенной выше цитате из документа " Routing & Navigation.
  • Отписаться от глобальных наблюдаемых, общих для компонентов, которые предоставляются через службу Angular, например, так как они могут быть подписаны несколько раз, пока компонент инициализирован.
  • Не нужно отписываться от внутренних наблюдаемых службы в области приложения, так как эта служба никогда не будет уничтожена, если только не уничтожено все ваше приложение, нет реальной причины отменить подписку и нет шансов утечки памяти. (6).

    Примечание. Что касается сервисов с определенной областью, то есть поставщиков компонентов, они уничтожаются при уничтожении компонента. В этом случае, если мы подписываемся на какой-либо наблюдаемый внутри этого провайдера, мы должны рассмотреть возможность отписаться от него, используя ловушку жизненного цикла OnDestroy которая будет вызываться при разрушении службы, в соответствии с документацией.
  • Используйте абстрактную технику, чтобы избежать путаницы в коде, которая может возникнуть в результате отписки. Вы можете управлять своими подписками с помощью takeUntil (3) или использовать этот пакет npm , упомянутый в (4). Самый простой способ отписаться от Observables в Angular.
  • Всегда отписаться от FormGroup наблюдаемых как form.valueChanges и form.statusChanges
  • Всегда отписывайтесь от наблюдаемых в сервисе Renderer2 таких как renderer2.listen
  • Отмените подписку от всех других наблюдаемых в качестве шага защиты от утечки памяти, пока Angular Docs явно не сообщит нам, какие наблюдаемые необязательны для отмены подписки (Проверьте проблему: (5) Документация для RxJS Unsubscribeing (Open)).
  • Бонус: всегда используйте Angular способы привязки событий, такие как HostListener как angular заботится об удалении прослушивателей событий, если это необходимо, и предотвращает любую потенциальную утечку памяти из-за привязок событий.

Хороший последний совет: если вы не знаете, автоматически ли отписывается/завершается или не complete заметка, добавьте complete обратный вызов для subscribe(...) и проверьте, вызывается ли он при уничтожении компонента.

Ответ 5

Это зависит. Если при вызове someObservable.subscribe() вы начинаете удерживать некоторый ресурс, который должен быть освобожден вручную, когда жизненный цикл вашего компонента завершен, тогда вы должны вызвать theSubscription.unsubscribe(), чтобы предотвратить утечку памяти.

Давайте более подробно рассмотрим ваши примеры:

getHero() возвращает результат http.get(). Если вы посмотрите в исходный код angular 2 , http.get() создает два прослушивателя событий:

_xhr.addEventListener('load', onLoad);
_xhr.addEventListener('error', onError);

и вызывая unsubscribe(), вы можете отменить запрос, а также слушателей:

_xhr.removeEventListener('load', onLoad);
_xhr.removeEventListener('error', onError);
_xhr.abort();

Обратите внимание, что _xhr является специфичным для платформы, но я считаю безопасным предположить, что это XMLHttpRequest() в вашем случае.

Обычно это достаточное количество доказательств, гарантирующих ручной вызов unsubscribe(). Но согласно WHATWG spec, XMLHttpRequest() подлежит сборке мусора после его завершения, даже если к нему подключены слушатели событий, Поэтому я предполагаю, что почему angular 2 официальное руководство опускает unsubscribe() и позволяет GC очистить слушателей.

Что касается вашего второго примера, это зависит от реализации params. На сегодняшний день официальное руководство angular больше не отображает отмену подписки на params. Я снова просмотрел src и обнаружил, что params является просто BehaviorSubject. Поскольку ни один из прослушивателей или таймеров не использовался, и глобальные переменные не создавались, следует опустить unsubscribe().

Суть в том, что вы всегда вызываете unsubscribe() в качестве защиты от утечки памяти, если вы не уверены, что выполнение наблюдаемого не создает глобальных переменных, добавлять прослушиватели событий, устанавливать таймеры или делать что-либо иначе это приведет к утечке памяти.

Если вы сомневаетесь, посмотрите на реализацию этого наблюдаемого. Если наблюдаемый написал некоторую логику очистки в свой unsubscribe(), который обычно является функцией, возвращаемой конструктором, тогда у вас есть веские основания серьезно подумать о вызове unsubscribe().

Ответ 6

Angular 2 официальная документация дает объяснение, когда отказаться от подписки и когда ее можно безопасно игнорировать. Посмотрите на эту ссылку:

https://angular.io/docs/ts/latest/cookbook/component-communication.html#!#bidirectional-service

Ищите абзац с заголовком Родитель и дети обмениваются данными через службу, а затем синим полем:

Обратите внимание, что мы фиксируем подписку и отписываем подписку при уничтожении AstronautComponent. Это шаг защиты от утечки памяти. В этом приложении нет реального риска, так как время жизни AstronautComponent совпадает с временем жизни самого приложения. Это не всегда было бы правдой в более сложном приложении.

Мы не добавляем этот защитник к MissionControlComponent, потому что, как родительский элемент, он контролирует время жизни MissionService.

Надеюсь, это поможет вам.

Ответ 7

На основании: Использование наследования класса для привязки к жизненному циклу с угловым 2 компонентом

Другой общий подход:

export abstract class UnsubscribeOnDestroy implements OnDestroy {
  protected d$: Subject<any>;

  constructor() {
    this.d$ = new Subject<void>();

    const f = this.ngOnDestroy;
    this.ngOnDestroy = () => {
      f();
      this.d$.next();
      this.d$.complete();
    };
  }

  public ngOnDestroy() {
    // no-op
  }

}

Ответ 8

Поскольку seangwright solution (Edit 3) представляется очень полезным, я также обнаружил, что это боль, чтобы упаковать эту функцию в базовый компонент и намекнуть другим партнерам по проекту, чтобы помнить о вызове super() в ngOnDestroy для активации этой функции.

Этот ответ обеспечивает способ освобождения от супервызов и делает "componentDestroyed $" ядром базового компонента.

class BaseClass {
    protected componentDestroyed$: Subject<void> = new Subject<void>();
    constructor() {

        /// wrap the ngOnDestroy to be an Observable. and set free from calling super() on ngOnDestroy.
        let _$ = this.ngOnDestroy;
        this.ngOnDestroy = () => {
            this.componentDestroyed$.next();
            this.componentDestroyed$.complete();
            _$();
        }
    }

    /// placeholder of ngOnDestroy. no need to do super() call of extended class.
    ngOnDestroy() {}
}

И тогда вы можете использовать эту функцию свободно, например:

@Component({
    selector: 'my-thing',
    templateUrl: './my-thing.component.html'
})
export class MyThingComponent extends BaseClass implements OnInit, OnDestroy {
    constructor(
        private myThingService: MyThingService,
    ) { super(); }

    ngOnInit() {
        this.myThingService.getThings()
            .takeUntil(this.componentDestroyed$)
            .subscribe(things => console.log(things));
    }

    /// optional. not a requirement to implement OnDestroy
    ngOnDestroy() {
        console.log('everything works as intended with or without super call');
    }

}

Ответ 9

Официальный ответ Edit # 3 (и варианты) работает хорошо, но то, что меня заводит, - это "грязная" бизнес-логика вокруг наблюдаемой подписки.

Здесь другой подход с использованием оберток.

Экспериментальный код Warining:

Файл subscribeAndGuard.ts используется для создания нового расширения Observable для переноса .subscribe() и внутри него для обертывания ngOnDestroy().
Использование такое же, как .subscribe(), за исключением дополнительного первого параметра, ссылающегося на компонент.

import { Observable } from 'rxjs/Observable';
import { Subscription } from 'rxjs/Subscription';

const subscribeAndGuard = function(component, fnData, fnError = null, fnComplete = null) {

  // Define the subscription
  const sub: Subscription = this.subscribe(fnData, fnError, fnComplete);

  // Wrap component onDestroy
  if (!component.ngOnDestroy) {
    throw new Error('To use subscribeAndGuard, the component must implement ngOnDestroy');
  }
  const saved_OnDestroy = component.ngOnDestroy;
  component.ngOnDestroy = () => {
    console.log('subscribeAndGuard.onDestroy');
    sub.unsubscribe();
    // Note: need to put original back in place
    // otherwise 'this' is undefined in component.ngOnDestroy
    component.ngOnDestroy = saved_OnDestroy;
    component.ngOnDestroy();

  };

  return sub;
};

// Create an Observable extension
Observable.prototype.subscribeAndGuard = subscribeAndGuard;

// Ref: https://www.typescriptlang.org/docs/handbook/declaration-merging.html
declare module 'rxjs/Observable' {
  interface Observable<T> {
    subscribeAndGuard: typeof subscribeAndGuard;
  }
}

Вот компонент с двумя подписками: один с оберткой и один без. Единственное предостережение в том, что должен реализовать OnDestroy (при желании пустым телом), в противном случае Angular не знает, как вызвать завернутую версию.

import { Component, OnInit, OnDestroy } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import 'rxjs/Rx';
import './subscribeAndGuard';

@Component({
  selector: 'app-subscribing',
  template: '<h3>Subscribing component is active</h3>',
})
export class SubscribingComponent implements OnInit, OnDestroy {

  ngOnInit() {

    // This subscription will be terminated after onDestroy
    Observable.interval(1000)
      .subscribeAndGuard(this,
        (data) => { console.log('Guarded:', data); },
        (error) => { },
        (/*completed*/) => { }
      );

    // This subscription will continue after onDestroy
    Observable.interval(1000)
      .subscribe(
        (data) => { console.log('Unguarded:', data); },
        (error) => { },
        (/*completed*/) => { }
      );
  }

  ngOnDestroy() {
    console.log('SubscribingComponent.OnDestroy');
  }
}

Демо-плункер здесь

Дополнительная заметка: Re Edit 3 - "Официальное" решение, это можно упростить, используя takeWhile() вместо takeUntil() перед подписками и просто булево, а не другое Observable в ngOnDestroy.

@Component({...})
export class SubscribingComponent implements OnInit, OnDestroy {

  iAmAlive = true;
  ngOnInit() {

    Observable.interval(1000)
      .takeWhile(() => { return this.iAmAlive; })
      .subscribe((data) => { console.log(data); });
  }

  ngOnDestroy() {
    this.iAmAlive = false;
  }
}

Ответ 10

Следуя ответу @seangwright, я написал абстрактный класс, который обрабатывает "бесконечные" наблюдаемые подписки в компонентах:

import { OnDestroy } from '@angular/core';
import { Subscription } from 'rxjs/Subscription';
import { Subject } from 'rxjs/Subject';
import { Observable } from 'rxjs/Observable';
import { PartialObserver } from 'rxjs/Observer';

export abstract class InfiniteSubscriberComponent implements OnDestroy {
  private onDestroySource: Subject<any> = new Subject();

  constructor() {}

  subscribe(observable: Observable<any>): Subscription;

  subscribe(
    observable: Observable<any>,
    observer: PartialObserver<any>
  ): Subscription;

  subscribe(
    observable: Observable<any>,
    next?: (value: any) => void,
    error?: (error: any) => void,
    complete?: () => void
  ): Subscription;

  subscribe(observable: Observable<any>, ...subscribeArgs): Subscription {
    return observable
      .takeUntil(this.onDestroySource)
      .subscribe(...subscribeArgs);
  }

  ngOnDestroy() {
    this.onDestroySource.next();
    this.onDestroySource.complete();
  }
}

Чтобы использовать его, просто добавьте его в свой угловой компонент и вызовите метод subscribe() следующим образом:

this.subscribe(someObservable, data => doSomething());

Он также принимает ошибку и выполняет обратные вызовы, как обычно, объект наблюдателя или вообще не вызывает обратные вызовы. Не забудьте вызвать super.ngOnDestroy() если вы также реализуете этот метод в дочернем компоненте.

Найдите здесь дополнительную ссылку от Ben Lesh: RxJS: Dont Unsubscribe.

Ответ 11

Я попробовал решение seangwright (Edit 3)

Это не работает для Observable, созданного таймером или интервалом.

Однако я получил его, используя другой подход:

import { Component, OnDestroy, OnInit } from '@angular/core';
import 'rxjs/add/operator/takeUntil';
import { Subject } from 'rxjs/Subject';
import { Subscription } from 'rxjs/Subscription';
import 'rxjs/Rx';

import { MyThingService } from '../my-thing.service';

@Component({
   selector: 'my-thing',
   templateUrl: './my-thing.component.html'
})
export class MyThingComponent implements OnDestroy, OnInit {
   private subscriptions: Array<Subscription> = [];

  constructor(
     private myThingService: MyThingService,
   ) { }

  ngOnInit() {
    const newSubs = this.myThingService.getThings()
        .subscribe(things => console.log(things));
    this.subscriptions.push(newSubs);
  }

  ngOnDestroy() {
    for (const subs of this.subscriptions) {
      subs.unsubscribe();
   }
 }
}

Ответ 12

Мне нравятся последние два ответа, но я столкнулся с проблемой, если подкласс ссылался на "this" в ngOnDestroy.

Я изменил это так, и похоже, что он разрешил эту проблему.

export abstract class BaseComponent implements OnDestroy {
    protected componentDestroyed$: Subject<boolean>;
    constructor() {
        this.componentDestroyed$ = new Subject<boolean>();
        let f = this.ngOnDestroy;
        this.ngOnDestroy = function()  {
            // without this I was getting an error if the subclass had
            // this.blah() in ngOnDestroy
            f.bind(this)();
            this.componentDestroyed$.next(true);
            this.componentDestroyed$.complete();
        };
    }
    /// placeholder of ngOnDestroy. no need to do super() call of extended class.
    ngOnDestroy() {}
}

Ответ 13

Обычно вам нужно отказаться от подписки, когда компоненты будут уничтожены, но Angular будет обрабатывать все больше и больше, когда мы идем, например, в новой младшей версии Angular4, у них есть этот раздел для маршрутизации отписки:

Вам нужно отказаться от подписки?

Как описано в ActivatedRoute: универсальный магазин для раздела информации о маршруте Маршрутизация и Навигация, Маршрутизатор управляет наблюдаемыми им обеспечивает и локализует подписки. Подписки очищается, когда компонент уничтожается, защищая от памяти утечки, поэтому вам не нужно отписываться от маршрута paramMap Наблюдаемое.

Также приведенный ниже пример - хороший пример из Angular, чтобы создать компонент и уничтожить его после, посмотрите, как компонент реализует OnDestroy, если вам нужно onInit, вы также можете реализовать его в своем компоненте, например, реализуете OnInit, OnDestroy

import { Component, Input, OnDestroy } from '@angular/core';  
import { MissionService } from './mission.service';
import { Subscription }   from 'rxjs/Subscription';

@Component({
  selector: 'my-astronaut',
  template: `
    <p>
      {{astronaut}}: <strong>{{mission}}</strong>
      <button
        (click)="confirm()"
        [disabled]="!announced || confirmed">
        Confirm
      </button>
    </p>
  `
})

export class AstronautComponent implements OnDestroy {
  @Input() astronaut: string;
  mission = '<no mission announced>';
  confirmed = false;
  announced = false;
  subscription: Subscription;

  constructor(private missionService: MissionService) {
    this.subscription = missionService.missionAnnounced$.subscribe(
      mission => {
        this.mission = mission;
        this.announced = true;
        this.confirmed = false;
    });
  }

  confirm() {
    this.confirmed = true;
    this.missionService.confirmMission(this.astronaut);
  }

  ngOnDestroy() {
    // prevent memory leak when component destroyed
    this.subscription.unsubscribe();
  }
}

Ответ 14

Еще одно короткое дополнение к вышеупомянутым ситуациям:

  • Всегда отказаться от подписки, когда новые значения в подписанном потоке больше не требуются или не имеют значения, это приведет к уменьшению числа триггеров и увеличению производительности в нескольких случаях. Случаи, такие как компоненты, где подписанные данные/события больше не существуют или требуется новая подписка на все новые потоки (обновление и т.д.), Является хорошим примером отмены подписки.

Ответ 15

В случае необходимости отписки можно использовать следующий оператор для метода наблюдаемой трубы

import { Observable, Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { OnDestroy } from '@angular/core';

export const takeUntilDestroyed = (componentInstance: OnDestroy) => <T>(observable: Observable<T>) => {
  const subjectPropertyName = '__takeUntilDestroySubject__';
  const originalOnDestroy = componentInstance.ngOnDestroy;
  const componentSubject = componentInstance[subjectPropertyName] as Subject<any> || new Subject();

  componentInstance.ngOnDestroy = (...args) => {
    originalOnDestroy.apply(componentInstance, args);
    componentSubject.next(true);
    componentSubject.complete();
  };

  return observable.pipe(takeUntil<T>(componentSubject));
};

это можно использовать так:

import { Component, OnDestroy, OnInit } from '@angular/core';
import { Observable } from 'rxjs';

@Component({ template: '<div></div>' })
export class SomeComponent implements OnInit, OnDestroy {

  ngOnInit(): void {
    const observable = Observable.create(observer => {
      observer.next('Hello');
    });

    observable
      .pipe(takeUntilDestroyed(this))
      .subscribe(val => console.log(val));
  }

  ngOnDestroy(): void {
  }
}

Оператор переносит метод компонента ngOnDestroy.

Важно: оператор должен быть последним в наблюдаемой трубе.

Ответ 16

в SPA применения в функции ngOnDestroy (угловой) Жизненным Циклом Для каждой подписки вы должны отказаться его. преимущество => чтобы государство не стало слишком тяжелым.

например: в компоненте 1:

import {UserService} from './user.service';

private user = {name: 'test', id: 1}

constructor(public userService: UserService) {
    this.userService.onUserChange.next(this.user);
}

в сервисе:

import {BehaviorSubject} from 'rxjs/BehaviorSubject';

public onUserChange: BehaviorSubject<any> = new BehaviorSubject({});

в компоненте 2:

import {Subscription} from 'rxjs/Subscription';
import {UserService} from './user.service';

private onUserChange: Subscription;

constructor(public userService: UserService) {
    this.onUserChange = this.userService.onUserChange.subscribe(user => {
        console.log(user);
    });
}

public ngOnDestroy(): void {
    // note: Here you have to be sure to unsubscribe to the subscribe item!
    this.onUserChange.unsubscribe();
}

Ответ 17

Для обработки подписки я использую класс "Unsubscriber".

Вот класс Unsubscriber.

export class Unsubscriber implements OnDestroy {
  private subscriptions: Subscription[] = [];

  addSubscription(subscription: Subscription | Subscription[]) {
    if (Array.isArray(subscription)) {
      this.subscriptions.push(...subscription);
    } else {
      this.subscriptions.push(subscription);
    }
  }

  unsubscribe() {
    this.subscriptions
      .filter(subscription => subscription)
      .forEach(subscription => {
        subscription.unsubscribe();
      });
  }

  ngOnDestroy() {
    this.unsubscribe();
  }
}

И Вы можете использовать этот класс в любом компоненте/Сервис/Эффект и т.д.

Пример:

class SampleComponent extends Unsubscriber {
    constructor () {
        super();
    }

    this.addSubscription(subscription);
}

Ответ 18

Вы можете использовать последний класс Subscription, чтобы отказаться от подписки на Observable с не таким грязным кодом.

Мы можем сделать это с normal variable, но это будет override the last subscription при каждой новой подписке, поэтому избегайте этого, и этот подход очень полезен, когда вы имеете дело с большим количеством Obseravables и типа Obeservables, таких как BehavoiurSubject и Subject

подписка

Представляет одноразовый ресурс, такой как выполнение Observable. У подписки есть один важный метод - отписаться, который не принимает аргументов и просто удаляет ресурс, удерживаемый подпиской.

Вы можете использовать это двумя способами,

  • вы можете напрямую добавить подписку в массив подписок

     subscriptions:Subscription[] = [];
    
     ngOnInit(): void {
    
       this.subscription.push(this.dataService.getMessageTracker().subscribe((param: any) => {
                //...  
       }));
    
       this.subscription.push(this.dataService.getFileTracker().subscribe((param: any) => {
            //...
        }));
     }
    
     ngOnDestroy(){
        // prevent memory leak when component destroyed
        this.subscriptions.forEach(s => s.unsubscribe());
      }
    
  • используя add() из Subscription

    subscriptions = new Subscription();
    
    this.subscriptions.add(subscribeOne);
    this.subscriptions.add(subscribeTwo);
    
    ngOnDestroy() {
      this.subscriptions.unsubscribe();
    }
    

Subscription может хранить дочерние подписки и безопасно отписываться от них всех. Этот метод обрабатывает возможные ошибки (например, если какие-либо дочерние подписки равны нулю).

Надеюсь это поможет.. :)

Ответ 19

Если вы используете Http-сервис, вам не нужно отписывать его. Он автоматически отменит подписку.

Но если вы используете настраиваемую службу, вы должны отказаться от нее, используя метод ngOnDestroy() в конце вашей программы/класса.