Angular / RxJs Kiedy powinienem zrezygnować z subskrypcji "subskrypcja"

Kiedy należy przechowywać instancje Subscription i wywoływać unsubscribe() podczas cyklu życia NgOnDestroy, a kiedy po prostu je ignorować?

Zapisywanie wszystkich subskrypcji wprowadza sporo bałaganu w kodzie komponentów.

HTTP Client Guide ignoruj subskrypcje w ten sposób:

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

W tym samym czasie Przewodnik po trasie i nawigacji mówi, że:

W końcu, będziemy poruszać się gdzie indziej. Router usunie ten komponent z DOM i zniszczy to. Musimy posprzątać po sobie, zanim to się stanie. W szczególności musimy zrezygnować z subskrypcji, zanim Angular zniszczy komponent. Nieprzestrzeganie tego może spowodować wyciek pamięci.

Rezygnujemy z naszej Observable metodą 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();
}
Author: Jota.Toledo, 2016-06-24

14 answers

W 2018 Roku, Po Raz Pierwszy W Historii, Pojawiła Się Nowa Wersja Gry.]}

W ostatnim odcinkuAdventures in Angular Ben Lesh i Ward Bell omawiają kwestie dotyczące tego, jak/kiedy zrezygnować z subskrypcji w komponencie. Dyskusja zaczyna się około 1:05: 30.

Ward wspomina right now there's an awful takeUntil dance that takes a lot of machinery i Shai Reznik wspomina Angular handles some of the subscriptions like http and routing.

W odpowiedzi Ben wspomina, że obecnie trwają dyskusje, aby umożliwić obserwowalne połączenie się z wydarzeniami cyklu życia elementu kątowego, A Ward sugeruje obserwowalne zdarzenia cyklu życia, które komponent może zapisać jako sposób poznania, kiedy zakończyć Obserwable utrzymywane jako stan wewnętrzny komponentu.

To powiedziawszy, głównie potrzebujemy rozwiązań teraz, więc oto kilka innych zasobów.

  1. Rekomendacja dla wzorca takeUntil() od członka Rxjs Core team Nicholasa Jamiesona i reguła tslinta, która pomoże go wyegzekwować. https://blog.angularindepth.com/rxjs-avoiding-takeuntil-leaks-fb5182d047ef

  2. Lekki pakiet npm, który wyświetla obserwowalny operator, który bierze instancję komponentu (this) jako parametr i automatycznie wypisuje się podczas ngOnDestroy. https://github.com/NetanelBasal/ngx-take-until-destroy

  3. Kolejna odmiana powyższego z nieco lepszą ergonomią, jeśli nie robisz kompilacji AOT (ale wszyscy powinniśmy robić AOT teraz). https://github.com/smnbbrv/ngx-rx-collector

  4. Niestandardowa dyrektywa *ngSubscribe, która działa jak rura asynchroniczna, ale tworzy osadzony widok w szablonie, dzięki czemu można odwoływać się do wartości "unwrapped" w całym szablonie. https://netbasal.com/diy-subscription-handling-directive-in-angular-c8f6e762697f

Wspominam w komentarzu do bloga Nicholasa, że nadmierne użycie takeUntil() może być znakiem, że Twój komponent stara się zrobić zbyt wiele i należy wziąć pod uwagę podział istniejących komponentów na Feature i Presentational składniki. Można wtedy | async obserwowalny z komponentu funkcji do Input komponentu prezentacyjnego, co oznacza, że nigdzie nie są potrzebne subskrypcje. Dowiedz się więcej o tym podejściu tutaj

W 2017 Roku, W Ramach Projektu "The World Of Warcraft 2017", W 2018 Roku, W Ramach Projektu "The World Of Warcraft 2018", W 2019 Roku, W 2019 Roku, W 2019 Roku.]}

Rozmawiałem z Wardem Bellem o tym pytaniu na NGConf (pokazałem mu nawet tę odpowiedź który powiedział, że jest poprawny), ale powiedział mi, że zespół docs dla Angular ma rozwiązanie tego pytania, które jest niepublikowane (choć pracują nad jego zatwierdzeniem). Powiedział mi również, że mogę zaktualizować moją odpowiedź SO z nadchodzącej oficjalnej rekomendacji.

Rozwiązaniem, którego powinniśmy użyć w przyszłości, jest dodanie pola private ngUnsubscribe = new Subject(); do wszystkich komponentów, które mają .subscribe() wywołania Observable S w kodzie swojej klasy.

Następnie wywołujemy this.ngUnsubscribe.next(); this.ngUnsubscribe.complete(); w naszych metodach ngOnDestroy().

The tajnym sosem (jak zauważył już @ metamaker) jest wywołanie takeUntil(this.ngUnsubscribe) przed każdym z naszych wywołań .subscribe(), co zagwarantuje, że wszystkie subskrypcje zostaną wyczyszczone po zniszczeniu komponentu.

Przykład:

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

Uwaga: ważne jest, aby dodać operator takeUntil jako ostatni, aby zapobiec wyciekom z pośrednich obserwabli w łańcuchu operatora.

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

Źródło 5

Angular tutorial, the Rozdział Routing stwierdza teraz, co następuje: "Router zarządza dostępnymi obserwacjami i lokalizuje subskrypcje. Subskrypcje są czyszczone, gdy komponent jest zniszczony, chroniąc przed wyciekami pamięci, więc nie musimy rezygnować z params trasy obserwowalne."- Marek Rajcok

W tym artykule omówiono problemy z Github dla Angular docs dotyczące routerów, w których Ward Bell wspomina, że wyjaśnienie tego wszystkiego znajduje się w dzieła.

--- Edycja 1

Źródło 4

W tym wideo z NgEurope Rob Wormald mówi również, że nie musisz rezygnować z routera Obserwable. Wspomina również o usłudze http i ActivatedRoute.params w tym wideoz listopada 2016 roku .

- - - Oryginalna Odpowiedź

TLDR:

Na to pytanie są (2) rodzaje Observables - skończona wartość i nieskończona wartość.

http Observables produkuj skończone (1) wartości i coś w rodzaju DOM event listener Observables twórz nieskończone wartości.

Jeśli ręcznie wywołasz subscribe (nie używając rury asynchronicznej), to unsubscribe Z infinite Observables.

Nie martw się o skończone one, RxJs zajmą się nimi.

Źródło 1

Znalazłem odpowiedź Roba Wormalda w Gitterze Angular tutaj.

Stwierdza (zreorganizowałem dla jasności i nacisk jest mój)

If its a single-value-sequence (jak żądanie http) nie jest konieczne ręczne czyszczenie (zakładając, że zapisujesz się do kontrolera ręcznie)

W 2007 roku, po raz pierwszy w historii, został wybrany do Izby Reprezentantów, a w 2008 roku został wybrany do Izby Reprezentantów.]}

Jeśli jest ciągiem nieskończonym, w tym celu należy skontaktować się z Działem obsługi klienta.]}

On wspomina w tym wideo youtube {[46] } na Obserwabli, że they clean up after themselves... w kontekście Obserwabli, które complete (jak obietnice, które zawsze się uzupełniają, ponieważ zawsze wytwarzają 1 wartość i kończą - nigdy nie martwiliśmy się o wypisanie się z obietnic, aby upewnić się, że oczyszczają xhr słuchacze zdarzeń, prawda?).

Źródło 2

Również w Rangle guide to Angular 2 czytamy

W większości przypadków nie będziemy musieli wyraźnie zadzwoń do metody anulowania subskrypcji, chyba że chcemy anulować wcześniej lub nasz Observable ma dłuższą żywotność niż nasza subskrypcja. Domyślnym zachowaniem obserwowalnych operatorów jest pozbycie się subskrypcji tak szybko, jak to możliwe .complete() lub .komunikaty o błędach() są publikowane. Należy pamiętać, że RxJS został zaprojektowany do używania w "fire and forget" przez większość czasu.

Kiedy stosuje się frazę our Observable has a longer lifespan than our subscription?

Ma zastosowanie, gdy subskrypcja jest tworzona wewnątrz komponentu, który jest zniszczone przed (lub nie "długo" przed) Observable zakończy się.

Czytam to tak, że jeśli SUBSKRYBUJEMY http żądanie lub obserwowalne, które emituje 10 wartości, a nasz komponent zostanie zniszczony przed tym http żądanie zwraca lub 10 wartości zostały wyemitowane, nadal jesteśmy w porządku!

Gdy żądanie zwróci lub 10. wartość zostanie ostatecznie wyemitowana, Observable zakończy się i wszystkie zasoby zostaną wyczyszczone.

Źródło 3

Jeśli spojrzymy na ten przykład Z tego samego przewodnika Rangle widzimy, że Subscription do route.params wymaga unsubscribe(), ponieważ nie wiemy, kiedy te params przestaną się zmieniać (emitując nowe wartości).

Komponent może zostać zniszczony przez nawigację, w którym to przypadku paramy trasy prawdopodobnie nadal będą się zmieniać (technicznie mogą się zmieniać do czasu zakończenia aplikacji), a zasoby przydzielone w subskrypcji nadal będą przydzielane, ponieważ nie było completion.

 717
Author: seangwright,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-09-01 17:12:21

Nie musisz mieć kilku subskrypcji i wypisać się ręcznie. Użyj RxJS.Subject i takeUntil combo do obsługi abonamentów jak boss:

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

Alternatywne podejście , które zostało zaproponowane przez @acumartini w komentarzach , wykorzystuje takeWhilezamiast takuntil. Możesz go preferować, ale pamiętaj, że w ten sposób Twoje obserwowalne wykonanie nie zostanie anulowane na ngDestroy twojego komponentu (np. korzystanie z obliczeń lub oczekiwanie na dane z serwera). Metoda, która opiera się na takeUntil , nie ma tej wady i prowadzi do natychmiastowego anulowania wniosku. podziękowania dla @ AlexChe za szczegółowe wyjaśnienie w komentarzach .

Oto kod:

@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;
    }
}
 62
Author: metamaker,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-09-25 09:01:32

Klasa subskrypcji ma ciekawą funkcję:

Reprezentuje zasoby jednorazowe, takie jak wykonanie obserwowalnego. Subskrypcja ma jedną ważną metodę, anulowanie subskrypcji, która nie wymaga żadnego argumentu i po prostu usuwa zasoby posiadane przez subskrypcję.
ponadto subskrypcje mogą być grupowane za pomocą metody add (), która dołączy subskrypcję podrzędną do bieżącej subskrypcji. Gdy subskrypcja zostanie anulowana, wszystkie jej dzieci (i jego wnuki) zostaną również wypisane.

Możesz utworzyć zbiorczy obiekt subskrypcji, który grupuje wszystkie subskrypcje. Można to zrobić, tworząc pustą subskrypcję i dodając do niej subskrypcje za pomocą metody add(). Gdy komponent zostanie zniszczony, wystarczy zrezygnować z subskrypcji zbiorczej.

@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();
  }
}
 39
Author: Steven Liekens,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-08-17 20:21:52

To zależy. Jeśli wywołując someObservable.subscribe(), zaczniesz zatrzymywać jakiś zasób, który musi zostać ręcznie uwolniony po zakończeniu cyklu życia komponentu, powinieneś wywołać theSubscription.unsubscribe(), aby zapobiec wyciekowi pamięci.

Przyjrzyjmy się bliżej Twoim przykładom:]}

getHero() zwraca wynik http.get(). Jeśli spojrzysz w kątowy 2 kod źródłowy, http.get() tworzy dwa słuchacze zdarzeń:

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

I dzwoniąc unsubscribe(), możesz anulować żądanie, jak również słuchacze:

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

Zauważ, że _xhr jest specyficzna dla platformy, ale myślę, że można bezpiecznie założyć, że jest to XMLHttpRequest() w Twoim przypadku.

Zwykle jest to wystarczający dowód, aby nakazać ręczne wezwanie. Jednak zgodnie z tą specyfikacją WHATWG , XMLHttpRequest() jest poddawany zbieraniu śmieci po "zakończeniu", nawet jeśli są do niego dołączone słuchacze zdarzeń. Więc chyba dlatego angular 2 official guide pomija unsubscribe() i pozwala GC oczyścić słuchaczy.

Co do twojej drugiej przykład, zależy to od implementacji params. Od dziś oficjalny przewodnik angular nie pokazuje już rezygnacji z params. Zajrzałem do src ponownie i okazało się, że params jest tylko BehaviorSubject . Ponieważ nie użyto detektorów zdarzeń ani timerów ani nie stworzono zmiennych globalnych, należy bezpiecznie pominąć unsubscribe().

Reasumując twoje pytanie jest takie, że zawsze dzwoń unsubscribe() jako strażnik przed wyciekiem pamięci, chyba że jesteś pewien, że wykonanie z obserwowalnych nie tworzy zmiennych globalnych, nie dodaje detektorów zdarzeń, nie ustawia timerów ani nie robi niczego innego, co powoduje wycieki pamięci.

W razie wątpliwości, przyjrzyj się implementacji tego obserwowalnego. Jeśli obserwowalny zapisał jakąś logikę do swojej unsubscribe(), która jest zwykle funkcją zwracaną przez konstruktor, to masz dobry powód, aby poważnie rozważyć wywołanie unsubscribe().

 13
Author: evenstar,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-12-01 07:14:40

Niektóre z najlepszych praktyk dotyczących zapisów obserwowalnych wewnątrz elementów kątowych:

Cytat z Routing & Navigation

Kiedy subskrybujesz komponent obserwowalny w komponencie, prawie zawsze możesz zrezygnować z subskrypcji, gdy komponent zostanie zniszczony.

Istnieje kilka wyjątkowych obserwacji, gdzie nie jest to konieczne. Do wyjątków należą

Aktywowany i jego obserwatory są izolowane od samego routera. Router niszczy kierowany komponent, gdy nie jest już potrzebny, a wtryskiwany aktywator umiera wraz z nim.

Nie krępuj się i tak zrezygnować z subskrypcji. Jest nieszkodliwa i nigdy nie jest złą praktyką.

I odpowiadając na następujące linki:

Zebrałem kilka najlepszych praktyk dotyczących zapisów obserwowalnych wewnątrz elementów kątowych, aby podzielić się z wami: {]}

  • http obserwowalne wypisanie się jest warunkowe i powinniśmy rozważyć efekty wywołania zwrotnego "subscribe" uruchamianego po zniszczeniu komponentu indywidualnie. Wiemy, że angular usuwa i czyści http obserwowalny sam (1), (2). Choć to prawda z perspektywa zasobów opowiada tylko połowę historii. Załóżmy, że mówimy o bezpośrednim wywołaniu http z wewnątrz komponentu, a odpowiedź http trwała dłużej niż potrzeba, więc użytkownik zamknął komponent. Procedura obsługi subscribe() będzie nadal wywoływana, nawet jeśli komponent zostanie zamknięty i zniszczony. Może to mieć niepożądane skutki uboczne i w gorszych scenariuszach pozostawić stan aplikacji uszkodzony. Może również powodować wyjątki, jeśli kod w wywołaniu zwrotnym próbuje wywołać coś, co właśnie zostało wyrzucony. Jednak w tym samym czasie czasami są pożądane. Powiedzmy, że tworzysz klienta poczty e-mail i uruchamiasz dźwięk, gdy wiadomość e-mail zostanie wysłana - cóż, nadal chcesz, aby to nastąpiło, nawet jeśli komponent jest zamknięty (8).
  • nie ma potrzeby wypisywania się z obserwowalnych, które są kompletne lub błędne. Jednak nie ma w tym nic złego(7).
  • użyj AsyncPipe jak najwięcej, ponieważ automatycznie wypisuje się z obserwowalnego na zniszczenie komponentów.
  • Anuluj subskrypcję z ActivatedRoute obserwabli, takich jak route.params, jeśli są subskrybowane wewnątrz zagnieżdżonego (dodanego wewnątrz tpl za pomocą selektora komponentów) lub dynamicznego komponentu, ponieważ mogą być subskrybowane wiele razy, dopóki komponent macierzysty/host istnieje. Nie ma potrzeby wypisywania się z nich w innych scenariuszach, jak wspomniano w cytacie powyżej z Routing & Navigation docs.
  • wypisanie się z globalnych obserwabli współdzielonych między komponentami, które są odsłonięte przez kąt usługi, na przykład, ponieważ mogą być subskrybowane wiele razy tak długo, jak długo komponent jest inicjowany.
  • nie ma potrzeby wypisywania się z wewnętrznych obserwowalności usługi o zasięgu aplikacji, ponieważ ta usługa nigdy nie zostanie zniszczona, chyba że cała aplikacja zostanie zniszczona, nie ma prawdziwego powodu, aby z niej zrezygnować i nie ma szans na wycieki pamięci. (6).

    Uwaga: W odniesieniu do usług scoped, tj. dostawców komponentów, są one niszczone, gdy komponent jest zniszczony. W takim przypadku, jeśli SUBSKRYBUJEMY jakiekolwiek możliwe do zaobserwowania wewnątrz tego dostawcy, powinniśmy rozważyć wypisanie się z niego za pomocą OnDestroy hook cyklu życia, który zostanie wywołany, gdy usługa zostanie zniszczona, zgodnie z dokumentami.
  • użyj abstrakcyjnej techniki, aby uniknąć bałaganu w kodzie, który może być spowodowany anulowaniem napisów. Możesz zarządzać swoimi subskrypcjami za pomocą takeUntil (3) możesz też użyć tego npm pakiet wymieniony w (4) Najprostszy sposób wypisania się z obserwatoriów w Angular .
  • zawsze wypisuj się z FormGroup obserwatoriów takich jak form.valueChanges i form.statusChanges
  • zawsze wypisuj się z obserwatoriów Renderer2 usługi jak renderer2.listen
  • Anuluj subskrypcję z każdej obserwowalnej innej jako zabezpieczenie przed wyciekiem pamięci, dopóki Angular Docs nie powie nam, które obserwowalne obiekty nie są konieczne do anulowania subskrypcji(problem z zaznaczeniem: (5) Dokumentacja dla Rxjs anulowanie subskrypcji (Open)).
  • Bonus: Zawsze użyj kątowych sposobów wiązania zdarzeń, takich jak HostListener, ponieważ angular dobrze dba o usunięcie słuchaczy zdarzeń w razie potrzeby i zapobiega potencjalnemu wyciekowi pamięci z powodu wiązań zdarzeń.

Ostatnia wskazówka: Jeśli nie wiesz, czy obserwowalny jest automatycznie anulowany / zakończony, dodaj callback complete do subscribe(...) i sprawdź, czy zostanie wywołany po zniszczeniu komponentu.

 13
Author: Mouneer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-10-02 12:00:08

Oficjalna dokumentacja Angular 2 zawiera wyjaśnienie, kiedy zrezygnować z subskrypcji i kiedy można ją bezpiecznie zignorować. Zobacz ten link:

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

Poszukaj akapitu z nagłówkiem rodzic i dzieci komunikują się za pośrednictwem usługi , a następnie niebieskie pole:

Zauważ, że rejestrujemy subskrypcję i rezygnujemy z subskrypcji, gdy AstronautComponent jest zniszczony. To zabezpieczenie przed wyciekiem pamięci. Nie ma rzeczywistego ryzyka w tej aplikacji, ponieważ czas życia Astronautskładnik jest taki sam jak czas życia samej aplikacji. Nie zawsze byłoby to prawdą w bardziej złożonej aplikacji.

Nie dodajemy tego strażnika do komponentu MissionControlComponent, ponieważ jako rodzic kontroluje czas życia usługi Mission.

Mam nadzieję, że to ci pomoże.

 6
Author: Cerny,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-06-29 11:08:04

Ponieważ rozwiązanie seangwrighta (Edit 3) wydaje się bardzo przydatne, uznałem również, że upierdliwe jest spakowanie tej funkcji do komponentu bazowego i podpowiedzenie innym członkom zespołu projektu, aby pamiętali o wywołaniu super () na ngOnDestroy, aby aktywować tę funkcję.

Ta odpowiedź daje sposób na uwolnienie od super call i uczynienie "componentDestroyed$" rdzeniem komponentu bazowego.

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

I wtedy możesz swobodnie korzystać z tej funkcji na przykład:

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

}
 3
Author: Val,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-04-24 05:34:10

Na podstawie: Wykorzystanie dziedziczenia klas do łączenia się z cyklem życia 2 składowych

Inne ogólne podejście:

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
  }

}

I użycie:

@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));
    }
}
 3
Author: JoG,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-02-23 14:54:31

Oficjalna odpowiedź Edit # 3 (i odmiany) działa dobrze, ale to, co mnie denerwuje, to "zamulanie" logiki biznesowej wokół obserwowalnej subskrypcji.

Oto inne podejście przy użyciu opakowań.

Warning: Kod eksperymentalny

Plik subscribeAndGuard.ts {[18] } służy do tworzenia nowego obserwowalnego rozszerzenia do wrap .subscribe() i wewnątrz niego do wrap ngOnDestroy().
Użycie jest takie samo jak .subscribe(), z wyjątkiem dodatkowego pierwszego parametru odniesienie do komponentu.

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

Oto komponent z dwiema subskrypcjami, jedną z opakowaniem i jedną bez. Jedynym zastrzeżeniem jest to, że musi zaimplementować OnDestroy (z pustym ciałem w razie potrzeby), w przeciwnym razie Angular nie wie, aby wywołać wersję zawiniętą.

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

A demo plunker is here

Uwaga dodatkowa: Re Edit 3 - 'oficjalne' rozwiązanie, można to uprościć używając takeWhile () zamiast takeUntil () przed subskrypcji, A prosty boolean, a nie inny obserwowalny w 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;
  }
}
 2
Author: Richard Matsen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-17 22:03:08

Lubię dwie ostatnie odpowiedzi, ale doświadczyłem problemu, jeśli podklasa odwołuje się "this" w ngOnDestroy.

Zmodyfikowałem go tak, i wygląda na to, że rozwiązał ten problem.

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() {}
}
 2
Author: Scott Williams,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-09-28 19:51:06

Podążając za odpowiedzią @seangwright , napisałem klasę abstrakcyjną, która obsługuje "nieskończone" subskrypcje obserwowalne w komponentach:

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

Aby go użyć, wystarczy rozszerzyć go w składniku kątowym i wywołać metodę subscribe() w następujący sposób:

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

Akceptuje również błąd i wykonuje wywołania zwrotne jak zwykle, obiekt obserwatora, lub w ogóle nie wywołuje. Pamiętaj, aby wywołać super.ngOnDestroy(), Jeśli implementujesz tę metodę również w komponencie potomnym.

Znajdź tutaj dodatkowe odniesienie Ben Lesh: RxJS: Don ' t Unsubscribe .

 2
Author: Mau Muñoz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-04-19 23:34:36

I tried seangwright ' s solution (Edit 3)

To nie działa dla obserwowalnych, które są tworzone przez timer lub interwał.

Jednak udało mi się to przy użyciu innego podejścia:

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();
   }
 }
}
 1
Author: Jeff Tham,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-04-11 20:00:48

Zazwyczaj musisz zrezygnować z subskrypcji, gdy komponenty zostaną zniszczone, ale Angular będzie obsługiwać ją coraz bardziej, na przykład w nowej, mniejszej wersji Angular4, mają tę sekcję do routingu wypisania się:

Czy musisz zrezygnować z subskrypcji?

jak opisano w ActivatedRoute: punkt kompleksowej obsługi dla sekcji informacji o trasie Strona routingu i nawigacji, Router zarządza obserwowanymi zapewnia i lokalizuje subskrypcje. Na subskrypcje są czyszczenie po zniszczeniu komponentu, chroniąc przed pamięcią przecieki, więc nie musisz wypisywać się z paramapy trasy Obserwowalne.

Również poniższy przykład jest dobrym przykładem z Angular, aby utworzyć komponent i zniszczyć go po, spójrz jak komponent implementuje OnDestroy, jeśli potrzebujesz onInit, możesz również zaimplementować go w swoim komponencie, jak implementuje 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();
  }
}
 1
Author: Alireza,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-10-16 11:52:57

Kolejnym krótkim dodatkiem do wyżej wymienionych sytuacji jest:

  • zawsze Anuluj subskrypcję, gdy nowe wartości w subskrybowanym strumieniu nie są już wymagane lub nie mają znaczenia, spowoduje to znacznie mniejszą liczbę wyzwalaczy i zwiększenie wydajności w kilku przypadkach. Przypadki, takie jak komponenty, w których subskrybowane dane/zdarzenie już nie istnieje lub wymagana jest nowa subskrypcja zupełnie nowego strumienia (odświeżanie itp.) jest dobrym przykładem rezygnacji z subskrypcji.
 1
Author: kg11,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-06-16 12:49:59