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

когда я должен хранить Subscription экземпляры и вызывать 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();
}

14 ответов


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

в недавнем эпизоде приключения в Angular Ben Lesh и Ward Bell обсуждают вопросы о том, как/когда отказаться от подписки в компоненте. Обсуждение начнется примерно в 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.

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

тем не менее, нам в основном нужны решения сейчас, поэтому вот некоторые другие ресурсы.

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

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

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

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

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

--- Edit 3 - "Официальный" Решение (2017/04/09)

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

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

мы тогда зовем 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 оператор as последний для предотвращения утечек с промежуточными наблюдаемыми в цепочке операторов.

--- Edit 2 (2016/12/28)

Источник 5

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

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

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

Источник 4

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

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

TLDR:

для этого вопроса есть (2) Виды Observables -конечная значение и бесконечный значение.

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

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

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

Источник 1

я разыскал ответ от Роба Wormald в угловой по Gitter здесь.

он заявляет (Я реорганизован для ясности и акцента)

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

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

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

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

Источник 2

и в руководство "срыгнуть" угловой 2 она гласит:

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

когда фраза our Observable has a longer lifespan than our subscription применить?

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

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

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

Источник 3

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

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


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

import {Subject} from "rxjs/Subject";

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

    constructor(protected titleService: TitleService)
    {
        this.titleService.emitter1$
            .takeUntil(this.componentDestroyed$)
            .subscribe(
            (data: any) =>
            {
                // ... do something 1
            }
        );

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

        // ...

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

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

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

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

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

    constructor(protected titleService: TitleService)
    {
        this.titleService.emitter1$
            .takeWhile(() => this.alive)
            .subscribe(
            (data: any) =>
            {
                // ... do something 1
            }
        );

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

        // ...

        this.titleService.emitterN$
            .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;
    }
}

класс подписки имеет интересную особенность:

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

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

@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();
  }
}

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

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

getHero() возвращает результат http.get(). Если вы посмотрите в угловое 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 это просто a BehaviorSubject. Поскольку прослушиватели событий или таймеры не использовались и глобальные переменные не создавались, следует с уверенностью опустить unsubscribe().

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

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


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

цитата Routing & Navigation

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

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

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

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

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

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

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

    Примечание: что касается сферы услуг, i.e поставщики компонентов, они уничтожаются при уничтожении компонента. В этом случае, если мы подписываемся на любой наблюдаемый внутри этого провайдера, мы должны рассмотреть возможность отмены подписки с помощью OnDestroy lifecycle hook, который будет вызываться, когда служба уничтожен, согласно документам.
  • используйте абстрактный метод, чтобы избежать любого беспорядка кода, который может быть результатом отписки. Вы можете управлять подписками с помощью takeUntil (3) или вы можете использовать этот npm пакета упомянутые в (4) Самый простой способ отписаться от наблюдаемых в Angular.
  • всегда отписаться от FormGroup наблюдаемых как form.valueChanges и form.statusChanges
  • обязательно отпишусь из наблюдаемых Renderer2 сервис как renderer2.listen
  • отписаться от каждого наблюдаемого else в качестве шага защиты от утечки памяти, пока угловые документы явно не скажут нам, какие наблюдаемые не нужны для отписки (Проверьте проблему:(5) документация для RxJS Unsubscribing (Open)).
  • бонус: всегда используйте угловые способы привязки событий, как HostListener as angular хорошо заботится об удалении прослушивателей событий, если это необходимо, и предотвращает любую потенциальную память утечка из-за Привязок событий.

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


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

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

найдите абзац с заголовком родители и дети общаются через службу и затем синяя коробка:

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

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

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


поскольку решение seangwright (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');
    }

}

основывается на : использование наследования класса для подключения к угловому жизненному циклу компонента 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
  }

}

и использовать :

@Component({
    selector: 'my-comp',
    template: ``
})
export class RsvpFormSaveComponent extends UnsubscribeOnDestroy implements OnInit {

    constructor() {
        super();
    }

    ngOnInit(): void {
      Observable.of('bla')
      .takeUntil(this.d$)
      .subscribe(val => console.log(val));
    }
}

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

вот еще один подход с использованием фантиков.

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

subscribeAndGuard.ТС используется для создания нового наблюдаемого расширения для wrap .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's 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');
  }
}

демо-plunker составляет здесь

дополнительно: Re Edit 3 - "Официальный" Решение, это можно упростить, используя takeWhile() вместо takeUntil () перед подписками и простое логическое, а не другое наблюдаемое в 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;
  }
}

мне нравятся последние два ответа, но у меня возникла проблема, если подкласс ссылается "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() {}
}

после ответа на @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() Если вы также реализуете этот метод в дочернем деталь.

найти здесь дополнительную ссылку Бен Леша:RxJS: не отказывайтесь от подписки.


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

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

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

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();
   }
 }
}

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

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

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

также приведенный ниже пример является хорошим примером из Angular для создания компонента и его уничтожения после, посмотрите, как компонент реализует OnDestroy, если вам нужен onInit, вы также можете реализовать его в своем компоненте, например implements 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();
  }
}

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

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