Angular / RxJs Kiedy powinienem zrezygnować z subskrypcji `Subskrypcji`


719

Kiedy należy przechowywać Subscriptioninstancje i wywoływać je unsubscribe()podczas cyklu życia NgOnDestroy, a kiedy mogę je po prostu zignorować?

Zapisanie wszystkich subskrypcji wprowadza sporo bałaganu do kodu komponentu.

Przewodnik klienta HTTP ignoruje takie subskrypcje:

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

W tym samym czasie Przewodnik trasy i nawigacji mówi:

W końcu będziemy nawigować gdzie indziej. Router usunie ten składnik z DOM i zniszczy go. Musimy po sobie posprzątać, zanim to nastąpi. W szczególności musimy anulować subskrypcję, zanim Angular zniszczy komponent. Niezastosowanie się do tego może spowodować wyciek pamięci.

Możemy zrezygnować z naszych Observablew ngOnDestroymetodzie.

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

20
Chyba Subscriptions, aby http-requestsmożna zignorować, jak nazywają tylko onNextraz, a potem nazywają onComplete. RouterZamiast nazywa onNextwielokrotnie i nigdy nie może zadzwonić onComplete(nie wiem o tym ...). To samo dotyczy Observables od Events. Sądzę, że tak powinno być unsubscribed.
Springrbua

1
@ gt6707a Strumień kończy się (lub nie kończy) niezależnie od jakichkolwiek obserwacji tego zakończenia. Oddzwaniania (obserwator) dostarczone do funkcji subskrypcji nie określają, czy zasoby są alokowane. Jest to wezwanie do subscribesiebie, które potencjalnie alokuje zasoby na wcześniejszym etapie.
seangwright

Odpowiedzi:


980

--- Edycja 4 - Dodatkowe zasoby (2018/09/01)

W ostatnim odcinku Adventures in Angular Ben Lesh i Ward Bell dyskutują na temat tego, jak / kiedy wypisać się z komponentu. Dyskusja rozpoczyna się około 1:05:30.

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

W odpowiedzi Ben wspomina, że ​​obecnie trwają dyskusje, aby umożliwić obserwatorom przyłączenie się do zdarzeń cyklu życia komponentu kątowego, a Ward sugeruje obserwowalność zdarzeń cyklu życia, do których komponent może się zapisać, aby dowiedzieć się, kiedy ukończyć obserwowalne utrzymywane jako stan wewnętrzny komponentu.

To powiedziawszy, teraz potrzebujemy rozwiązań, więc oto inne zasoby.

  1. Rekomendacja takeUntil()wzoru autorstwa członka zespołu RxJ, Nicholasa Jamiesona, oraz reguła tslint, aby pomóc w egzekwowaniu go. https://ncjamieson.com/avoiding-takeuntil-leaks/

  2. Lekki pakiet npm, który ujawnia obserwowalny operator, który przyjmuje instancję składnika ( 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 teraz robić AOT). https://github.com/smnbbrv/ngx-rx-collector

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

Wspominam w komentarzu na blogu Nicholasa, że ​​nadużywanie takeUntil()może być oznaką, że twój komponent próbuje zrobić zbyt wiele i że należy rozważyć rozdzielenie istniejących komponentów na komponenty Feature i Presentation . Następnie można | asyncobserwować z komponentu Feature do Inputkomponentu Presentation, co oznacza, że ​​nigdzie nie są potrzebne subskrypcje. Przeczytaj więcej o tym podejściu tutaj

--- Edytuj 3 - „Oficjalne” rozwiązanie (2017/04/09)

Rozmawiałem z Wardem Bellem na temat tego pytania w NGConf (nawet pokazałem mu tę odpowiedź, która, jak powiedział, była poprawna), ale powiedział mi, że zespół doktorów Angular ma rozwiązanie tego pytania, które nie zostało opublikowane (chociaż pracują nad tym, aby je zatwierdzić) ). Powiedział mi również, że mogę zaktualizować moją odpowiedź SO wraz z nadchodzącą oficjalną rekomendacją.

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

Następnie wywołujemy this.ngUnsubscribe.next(); this.ngUnsubscribe.complete();nasze ngOnDestroy()metody.

Tajny sos (jak już zauważył @metamaker ) ma wywoływać takeUntil(this.ngUnsubscribe)przed każdym z naszych .subscribe()połączeń, 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ć takeUntiloperatora jako ostatni, aby zapobiec wyciekom z pośrednimi obserwowalnymi w łańcuchu operatora.

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

Źródło 5

W samouczku Angular, rozdział Routing stwierdza teraz: „Router zarządza obserwowanymi przez siebie obiektami i lokalizuje subskrypcje. Subskrypcje są czyszczone po zniszczeniu komponentu, chroniąc przed wyciekiem pamięci, więc nie musimy rezygnować z subskrypcji trasa jest możliwa do zaobserwowania. ” - Mark Rajcok

Oto dyskusja na temat Github dla dokumentów Angular dotyczących Router Observables, w której Ward Bell wspomina, że ​​wyjaśnienie tego wszystkiego znajduje się w pracach.

--- Edytuj 1

Źródło 4

W tym filmie NgEurope Rob Wormald mówi również, że nie musisz rezygnować z subskrypcji Router Observables. Wspomina także o httpusłudze oraz ActivatedRoute.paramsw tym filmie z listopada 2016 r .

--- Oryginalna odpowiedź

TLDR:

Dla tego pytania istnieją (2) rodzaje Observables- wartość skończona i wartość nieskończona .

http Observablestworzy wartości skończone (1) i coś w rodzaju DOM event listener Observablestworzy wartości nieskończone .

Jeśli ręcznie wywołujesz subscribe(nie używając potoku asynchronicznego), to unsubscribez nieskończoności Observables .

Nie przejmuj się skończonymi , RxJszajmę się nimi.

Źródło 1

Tutaj odnalazłem odpowiedź Roba Wormalda w Angular's Gitter .

Mówi (zreorganizowałem dla jasności i podkreślenia jest moje)

jeśli jest to sekwencja o pojedynczej wartości (jak żądanie HTTP), ręczne czyszczenie nie jest konieczne (zakładając, że subskrybujesz ręcznie kontroler)

powinienem powiedzieć „jeśli jest to sekwencja, która się kończy ” (z których jedna sekwencja wartości, a la http, jest jedną)

jeśli jest to nieskończona sekwencja , należy zrezygnować z subskrypcji, którą wykonuje dla ciebie potok asynchroniczny

Wspomina również w tym filmie na YouTubie o Observables, który they clean up after themselves... w kontekście Observables, które complete(jak Obietnice, które zawsze się kończą, ponieważ zawsze generują 1 wartość i kończą się - nigdy nie martwiliśmy się o rezygnację z subskrypcji Obietnic, aby upewnić się, że posprzątają xhrwydarzenie słuchacze, prawda?).

Źródło 2

Również w przewodniku Rangle do Angulara 2 czytamy

W większości przypadków nie będziemy musieli jawnie wywoływać metody anulowania subskrypcji, chyba że chcemy wcześniej anulować subskrypcję lub nasz Observable ma dłuższą żywotność niż nasza subskrypcja. Domyślnym zachowaniem obserwowalnych operatorów jest usunięcie subskrypcji, gdy tylko zostaną opublikowane komunikaty .complete () lub .error (). Należy pamiętać, że RxJS został zaprojektowany do używania przez większość czasu w stylu „ognia i zapomnienia”.

Kiedy to wyrażenie ma our Observable has a longer lifespan than our subscriptionzastosowanie?

Ma to zastosowanie, gdy subskrypcja jest tworzona wewnątrz komponentu, który jest niszczony przed (lub „długo” przed) Observablezakończeniem.

Czytam to jako znaczące, że jeśli zasubskrybujemy httpżądanie lub obserwowalny, który emituje 10 wartości, a nasz komponent zostanie zniszczony przed httpzwróceniem tego żądania lub wysłaniem 10 wartości, nadal mamy się dobrze!

Gdy żądanie zostanie zwrócone lub dziesiąta wartość zostanie ostatecznie wysłana, Observablezostanie zakończone, a wszystkie zasoby zostaną oczyszczone.

Źródło 3

Jeśli spojrzymy na ten przykład z tego samego rangle kierować widzimy, że Subscriptionaby route.paramsnie wymagać unsubscribe(), bo nie wiemy, kiedy te paramsprzestaną zmianę (emitując nowe wartości).

Komponent może zostać zniszczony przez odejście, w którym to przypadku parametry trasy prawdopodobnie nadal będą się zmieniać (mogą technicznie zmieniać się aż do końca aplikacji), a zasoby przydzielone w subskrypcji będą nadal przydzielone, ponieważ nie było completion.


15
Wywołanie complete()sama w sobie nie wydaje się, aby oczyścić subskrypcji. Jakkolwiek wywołanie, next()a następnie complete()zadziałanie, uważam, że takeUntil()zatrzymuje się tylko po wygenerowaniu wartości, a nie po zakończeniu sekwencji.
Firefly

2
@seangwright Szybki test z członkiem typu Subjectwewnątrz komponentu i przełączaniem go w ngIfcelu wyzwolenia ngOnIniti ngOnDestroypokazania, że ​​podmiot i jego subskrypcje nigdy się nie zakończą ani nie zostaną zutylizowane (podłączyły finallyoperatora do subskrypcji). Muszę zadzwonić Subject.complete()w ngOnDestroytak subskrypcje mogą posprzątać po sobie.
Lars

3
Twoja --- Edycja 3 jest bardzo wnikliwa, dzięki! Mam tylko kolejne pytanie: jeśli korzystasz z tego takeUnitlpodejścia, nigdy nie musimy ręcznie rezygnować z subskrypcji żadnych obserwowalnych obiektów? Czy tak jest w przypadku? Ponadto, dlaczego musimy wywołać next()w ngOnDestroy, dlaczego po prostu nie zadzwonić complete()?
uglycode

6
@seangwright To rozczarowujące; dodatkowa płyta kotłowa jest denerwująca.
spongessuck 27.04.17

3
Edytuj 3 omówione w kontekście wydarzeń na medium.com/@benlesh/rxjs-dont-unsubscribe-6753ed4fda87
HankCa

89

Nie musisz mieć wielu subskrypcji i ręcznie zrezygnować z subskrypcji. Użyj komendy Subject i takeUntil do obsługi subskrypcji jak szef:

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

Alternatywne podejście , które zostało zaproponowane przez @acumartini w komentarzach , wykorzystuje takeWhile zamiast takeUntil . Możesz to preferować, ale pamiętaj, że w ten sposób twoje obserwowalne wykonanie nie zostanie anulowane po ngDestroy twojego komponentu (np. Gdy wykonujesz czasochłonne obliczenia lub czekasz na dane z serwera). Metoda oparta na takeUntil nie ma tej wady i prowadzi do natychmiastowego anulowania żądania. Dzięki @AlexChe za szczegółowe wyjaśnienia w komentarzach .

Oto kod:

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

1
Jeśli po prostu użyje bool, aby utrzymać stan, to jak sprawić, by „takeUntil” działał zgodnie z oczekiwaniami?
Val

5
Myślę, że istnieje znacząca różnica między używaniem takeUntila takeWhile. Pierwszy wypisuje się ze źródła obserwowalnego natychmiast po wystrzeleniu, podczas gdy drugi wypisuje się dopiero, gdy następna wartość zostanie wygenerowana przez obserwowalne źródło. Jeśli generowanie wartości przez obserwowalne źródło jest operacją pochłaniającą zasoby, wybranie jednego z nich może wykraczać poza preferencje stylu. Zobacz plunk
Alex Che

1
@AlexChe dziękuję za dostarczenie interesujących informacji! Jest to bardzo ważna kwestia dla ogólnego użycia takeUntilvs takeWhile, jednak nie w naszym konkretnym przypadku. Kiedy musimy anulować subskrypcję detektorów po zniszczeniu komponentu , sprawdzamy po prostu wartość boolowską , tak jak () => alivew takeWhile, więc żadne operacje zajmujące czas / pamięć nie są używane, a różnica dotyczy głównie stylizacji (ofc, w tym konkretnym przypadku).
metamaker

1
@metamaker Powiedzmy, że w naszym komponencie zasubskrybujemy an Observable, która wewnętrznie nextwydobywa kryptowalutę i wystrzeliwuje wydarzenie dla każdej wydobytej monety, a wydobycie jednej takiej monety zajmuje dzień. Dzięki takeUntilwypisamy się ze źródła wydobycie Observablenatychmiast po ngOnDestroywywołaniu podczas niszczenia naszego komponentu. W ten sposób Observablefunkcja wyszukiwania może natychmiast anulować działanie podczas tego procesu.
Alex Che

1
OTOH, jeśli używamy takeWhile, po ngOnDestoryprostu ustawiamy zmienną boolowską. Ale Observablefunkcja wyszukiwania może nadal działać przez jeden dzień i dopiero wtedy podczas nextpołączenia zda sobie sprawę, że nie ma aktywnych subskrypcji i musi zostać anulowana.
Alex Che

75

Klasa subskrypcji ma ciekawą funkcję:

Reprezentuje zasoby jednorazowe, takie jak wykonanie Observable. Subskrypcja ma jedną ważną metodę anulowania subskrypcji, która nie wymaga argumentów i po prostu usuwa zasoby posiadane przez subskrypcję.
Ponadto subskrypcje mogą być grupowane razem za pomocą metody add (), która dołącza podrzędną subskrypcję do bieżącej subskrypcji. Gdy subskrypcja zostanie anulowana, wszystkie jej dzieci (i wnuki) również zostaną anulowane.

Możesz utworzyć zagregowany obiekt subskrypcji, który grupuje wszystkie twoje subskrypcje. Robisz to, tworząc pustą subskrypcję i dodając do niej subskrypcje za pomocą jej add()metody. Gdy twój komponent zostanie zniszczony, wystarczy anulować subskrypcję 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();
  }
}

Używam tego podejścia. Zastanawiasz się, czy to jest lepsze niż stosowanie podejścia z takeUntil (), jak w przyjętej odpowiedzi .. wady?
Manuel Di Iorio,

Brak wad, o których jestem świadomy. Nie sądzę, żeby było lepiej, po prostu inaczej.
Steven Liekens,

2
Zobacz medium.com/@benlesh/rxjs-dont-unsubscribe-6753ed4fda87, aby uzyskać dalszą dyskusję na temat oficjalnego takeUntilpodejścia w porównaniu z tym podejściem polegającym na pobieraniu subskrypcji i telefonowaniu unsubscribe. (To podejście wydaje mi się o wiele czystsze.)
Josh Kelley

3
Jedna niewielka zaleta tej odpowiedzi: nie musisz sprawdzać, czy this.subscriptionsjest pusta
2023861,

1
Po prostu unikaj łączenia metod dodawania, takich jak sub = subsciption.add(..).add(..)ponieważ w wielu przypadkach daje nieoczekiwane wyniki github.com/ReactiveX/rxjs/issues/2769#issuecomment-345636477
Evgeniy Generalov

32

Niektóre z najlepszych praktyk dotyczących anulowania subskrypcji obserwowalnych w komponentach Angular:

Cytat z Routing & Navigation

Subskrybując obserwowalny element, prawie zawsze możesz zrezygnować z subskrypcji, gdy element zostanie zniszczony.

Istnieje kilka wyjątkowych obserwowalnych przypadków, w których nie jest to konieczne. Obserwowalne ActivatedRoute należą do wyjątków.

ActivatedRoute i jego obserwowalne elementy są izolowane od samego routera. Router niszczy przekierowany komponent, gdy nie jest już potrzebny, a wstrzyknięty ActivatedRoute umiera razem z nim.

Zrezygnuj z subskrypcji. Jest to nieszkodliwe i nigdy nie jest złą praktyką.

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

Zebrałem niektóre najlepsze praktyki dotyczące rezygnacji z obserwowalnych elementów wewnątrz Angular, aby podzielić się z Tobą:

  • httpobserwowalna rezygnacja z subskrypcji jest warunkowa i powinniśmy rozważyć skutki uruchomienia wywołania zwrotnego subskrypcji po zniszczeniu komponentu w poszczególnych przypadkach. Wiemy, że kątowy wypisuje się i czyści httpsamo obserwowalne (1) , (2) . Chociaż jest to prawda z punktu widzenia zasobów, opowiada tylko połowę historii. Powiedzmy, że mówimy o bezpośrednim wywoływaniu httpz komponentu, a httpodpowiedź zajęła więcej czasu, niż to konieczne, więc użytkownik zamknął komponent. Thesubscribe()moduł obsługi będzie nadal wywoływany, nawet jeśli komponent jest zamknięty i zniszczony. Może to mieć niepożądane skutki uboczne, aw gorszych scenariuszach stan aplikacji jest zepsuty. Może również powodować wyjątki, jeśli kod w wywołaniu zwrotnym próbuje wywołać coś, co zostało właśnie usunięte. Jednak jednocześnie są one pożądane. Załóżmy, że tworzysz klienta poczty e-mail i uruchamiasz dźwięk po zakończeniu wysyłania wiadomości e-mail - nadal chcesz, aby tak się stało, nawet jeśli składnik jest zamknięty ( 8 ).
  • Nie ma potrzeby anulowania subskrypcji obserwowanych elementów, które są kompletne lub błędne. Nie robi to jednak nic złego (7) .
  • Używaj AsyncPipejak najwięcej, ponieważ automatycznie wypisuje się z obserwowalnego zniszczenia elementu.
  • Anuluj subskrypcję ActivatedRouteobserwowalnych, tak route.paramsjakby subskrybowano je w zagnieżdżonym (dodanym w tpl z selektorem komponentów) lub komponencie dynamicznym, ponieważ mogą być subskrybowane wiele razy, dopóki istnieje komponent nadrzędny / host. Nie ma potrzeby wypisywania się z nich w innych scenariuszach, jak wspomniano w cytacie powyżej z Routing & Navigationdokumentów.
  • Zrezygnuj z globalnych obserwowalnych współdzielonych między komponentami, które są ujawniane na przykład za pośrednictwem usługi Angular, ponieważ mogą być subskrybowane wiele razy, dopóki komponent jest inicjowany.
  • Nie trzeba rezygnować z wewnętrznych obserwowalności usługi o zasięgu aplikacji, ponieważ usługa ta nigdy nie zostanie zniszczona, chyba że cała aplikacja zostanie zniszczona, nie ma prawdziwego powodu do rezygnacji z subskrypcji i nie ma szans na wycieki pamięci. (6) .

    Uwaga: Jeśli chodzi o usługi o zasięgu, tj. Dostawców komponentów, są one niszczone po zniszczeniu komponentu. W takim przypadku, jeśli zasubskrybujemy jakąkolwiek obserwowalną wewnątrz tego dostawcy, powinniśmy rozważyć rezygnację z subskrypcji za pomocą OnDestroyhaka cyklu życia, który zostanie wywołany, gdy usługa zostanie zniszczona, zgodnie z dokumentacją.
  • Użyj abstrakcyjnej techniki, aby uniknąć bałaganu w kodzie, który może wynikać z rezygnacji z subskrypcji. Możesz zarządzać swoimi subskrypcjami za pomocą takeUntil (3) lub możesz użyć tego npm pakietu wymienionego w (4) Najprostszym sposobem na wypisanie się z listy Obserwowalnych w Angular .
  • Zawsze wypisz się z FormGroupobserwowalnych obiektów, takich jak form.valueChangesiform.statusChanges
  • Zawsze wypisz się z obserwowalnych Renderer2usług, takich jakrenderer2.listen
  • Zrezygnuj z każdego możliwego do zaobserwowania kroku ochrony przed wyciekiem pamięci, dopóki Angular Docs nie powie nam wprost, które obserwowalne niepotrzebne są do rezygnacji z subskrypcji (Sprawdź problem: (5) Dokumentacja dotycząca rezygnacji z subskrypcji RxJS (otwarta) ).
  • Premia: Zawsze używaj Angularnych sposobów wiązania zdarzeń, HostListenerponieważ Angular dobrze dba o usunięcie detektorów zdarzeń w razie potrzeby i zapobiega potencjalnemu wyciekowi pamięci z powodu powiązań zdarzeń.

Dobra końcowa wskazówka : jeśli nie wiesz, czy obserwowalne jest automatycznie anulowane / ukończone, czy nie, dodaj completeoddzwonienie subscribe(...)i sprawdź, czy zostanie wywołane, gdy element zostanie zniszczony.


Odpowiedź na nr 6 nie jest do końca słuszna. Usługi są niszczone, a ich ngOnDestroywywoływanie następuje, gdy usługa jest świadczona na poziomie innym niż poziom główny, np. Jest jawnie udostępniana w komponencie, który później zostaje usunięty. W takich przypadkach należy zrezygnować z subskrypcji wewnętrznych usług obserwacyjnych
Drenai

@Drenai, dziękuję za komentarz i grzecznie się nie zgadzam. Jeśli komponent zostanie zniszczony, komponent, usługa i obserwowalne będą wszystkie GCed, a rezygnacja z subskrypcji będzie w tym przypadku bezużyteczna, chyba że zachowasz odniesienie do obserwowalnego gdziekolwiek z dala od komponentu (co nie jest logiczne, aby przeciekać stany komponentu globalnie pomimo wyboru usługi dla komponentu)
Mouneer,

Jeśli zniszczona usługa ma subskrypcję obserwowalnej należącej do innej usługi wyżej w hierarchii DI, GC nie wystąpi. Uniknąć tego scenariusza wypisywania IN ngOnDestroy, który jest zawsze o nazwie gdy usługi są zniszczone github.com/angular/angular/commit/...
Drenajów

1
@Drenai, sprawdź zaktualizowaną odpowiedź.
Mouneer

2
@ Tim Przede wszystkim, Feel free to unsubscribe anyway. It is harmless and never a bad practice.i jeśli chodzi o twoje pytanie, to zależy. Jeśli komponent potomny jest inicjowany wiele razy (na przykład dodawany wewnątrz ngIflub ładowany dynamicznie), należy anulować subskrypcję, aby uniknąć dodawania wielu subskrypcji do tego samego obserwatora. W przeciwnym razie nie ma takiej potrzeby. Ale wolę wypisać się z komponentu potomnego, ponieważ sprawia, że ​​jest on bardziej wielokrotnego użytku i odizolowany od tego, jak można go używać.
Mouneer,

18

To zależy. Jeśli dzwoniąc someObservable.subscribe(), zaczniesz utrzymywać zasób, który trzeba ręcznie zwolnić po zakończeniu cyklu życia komponentu, powinieneś zadzwonić, theSubscription.unsubscribe()aby zapobiec wyciekom pamięci.

Przyjrzyjmy się bliżej twoim przykładom:

getHero()zwraca wynik z http.get(). Jeśli spojrzysz na kod źródłowy Angular 2 , http.get()stwórz dwa detektory zdarzeń:

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

i dzwoniąc unsubscribe(), możesz anulować żądanie, a także słuchaczy:

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

Pamiętaj, że _xhrjest to specyficzne 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 uzasadnić ręczne unsubscribe()połączenie. Ale zgodnie z tą specyfikacją WHATWG , XMLHttpRequest()po „skończeniu” podlega on odśmiecaniu, nawet jeśli są do niego dołączone detektory zdarzeń. Sądzę więc, że dlatego oficjalny poradnik Angular 2 pomija unsubscribe()i pozwala GC posprzątać słuchaczy.

Jeśli chodzi o twój drugi przykład, zależy to od implementacji params. Na dzień oficjalny przewodnik kątowy nie pokazuje już rezygnacji z subskrypcji params. Znów zajrzałem do src i stwierdziłem, że paramsjest to po prostu BehaviorSubject . Ponieważ nie użyto żadnych detektorów zdarzeń ani timerów ani nie utworzono żadnych zmiennych globalnych, należy bezpiecznie je pominąć unsubscribe().

Najważniejsze w twoim pytaniu jest to, że zawsze zadzwoń unsubscribe()jako zabezpieczenie przed wyciekiem pamięci, chyba że masz pewność, że wykonanie obserwowalnego nie tworzy zmiennych globalnych, nie dodaje detektorów zdarzeń, ustawia liczniki czasu lub robi cokolwiek innego, co powoduje wycieki pamięci .

W razie wątpliwości przyjrzyj się realizacji tego obserwowalnego. Jeśli to, co obserwowalne, zapisało w niej trochę logiki czyszczenia unsubscribe(), która zwykle jest funkcją zwracaną przez konstruktora, masz dobry powód, aby poważnie rozważyć wywołanie unsubscribe().


6

Oficjalna dokumentacja Angular 2 zawiera wyjaśnienie, kiedy wypisać się, a kiedy można ją bezpiecznie zignorować. Spójrz na ten link:

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

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

Zauważ, że przechwytujemy subskrypcję i anulujemy subskrypcję, gdy AstronautComponent zostanie zniszczony. Jest to krok zabezpieczenia przed wyciekiem pamięci. W tej aplikacji nie ma rzeczywistego ryzyka, ponieważ czas życia AstronautComponent jest taki sam, jak czas życia samej aplikacji. Nie zawsze tak jest w przypadku bardziej złożonych aplikacji.

Nie dodajemy tej osłony do MissionControlComponent, ponieważ jako rodzic kontroluje żywotność usługi MissionService.

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


3
jako składnik nigdy nie wiesz, czy jesteś dzieckiem, czy nie. dlatego zawsze należy zrezygnować z subskrypcji jako najlepszej praktyki.
SeriousM,

1
W MissionControlComponent nie chodzi o to, czy jest rodzicem, czy nie, chodzi o to, że sam składnik zapewnia usługę. Gdy MissionControl zostanie zniszczony, podobnie jak usługa i wszelkie odniesienia do instancji usługi, dlatego nie ma możliwości wycieku.
zakończyć

6

Na podstawie: Korzystanie z dziedziczenia klas w celu dołączenia do cyklu życia komponentu Angular 2

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żyć :

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


1
To NIE działa poprawnie. Zachowaj ostrożność podczas korzystania z tego rozwiązania. Brakuje this.componentDestroyed$.next()połączenia takiego jak zaakceptowane rozwiązanie przez Sean powyżej ...
philn

4

Oficjalna odpowiedź na edycję nr 3 (i jej odmiany) działa dobrze, ale to, co mnie przyciąga, to „mętnienie” logiki biznesowej wokół obserwowalnej subskrypcji.

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

Warining: kod eksperymentalny

Plik subscribeAndGuard.ts służy do utworzenia nowego Obserowalnego rozszerzenia do zawinięcia .subscribe()oraz w nim do zawinięcia ngOnDestroy().
Użycie jest takie samo .subscribe(), z wyjątkiem dodatkowego pierwszego parametru odnoszącego się 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, a drugą bez. Jedynym zastrzeżeniem jest to, że musi implementować OnDestroy (z pustym ciałem, jeśli jest to pożądane), w przeciwnym razie Angular nie wie, aby wywołać wersję owinię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');
  }
}

Plunker demo jest tutaj

Dodatkowa uwaga: Re Edycja 3 - „Oficjalne” rozwiązanie, można to uprościć, używając takeWhile () zamiast takeUntil () przed subskrypcjami, oraz prostą wartość logiczną zamiast innej możliwej do zaobserwowania 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;
  }
}

3

Ponieważ rozwiązanie seangwrighta (Edycja 3) wydaje się bardzo przydatne, trudno mi było spakować tę funkcję do komponentu podstawowego i zasugerować innym członkom zespołu, aby pamiętali o wywołaniu super () na ngOnDestroy, aby aktywować tę funkcję.

Ta odpowiedź zapewnia sposób na uwolnienie się od super połączeń i uczynienie „componentDestroyed $” rdzeniem podstawowego komponentu.

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

Następnie 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

Zgodnie z odpowiedzią @seangwright napisałem abstrakcyjną klasę, która obsługuje subskrypcje „nieskończonych” obserwowalnych 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ć, po prostu rozciągnij go w składniku kątowym i wywołaj subscribe()metodę w następujący sposób:

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

Akceptuje także błąd i kompletne wywołania zwrotne, jak zwykle, obiekt obserwatora lub wcale nie wywołań zwrotnych. Pamiętaj, aby zadzwonić, super.ngOnDestroy()jeśli implementujesz tę metodę również w komponencie potomnym.

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


2

Próbowałem rozwiązania Seangwrighta (edycja 3)

To nie działa w przypadku Observable utworzonego przez timer lub interwał.

Udało mi się to jednak 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();
   }
 }
}

2

I podobnie jak w ostatnich dwóch odpowiedzi, ale wystąpił problem, gdy podklasa odwołuje "this"sięngOnDestroy .

Zmodyfikowałem go tak, aby wyglądał 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() {}
}

musisz użyć funkcji strzałki, aby powiązać „to”:this.ngOnDestroy = () => { f.bind(this)(); this.componentDestroyed$.complete(); };
Damsorian

2

W przypadku konieczności rezygnacji z subskrypcji można zastosować następujący operator metody obserwowalnej rury

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

można go użyć w następujący sposób:

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

Operator otacza metodę ngOnDestroy komponentu.

Ważne: operator powinien być ostatnim obserwowalnym przewodem.


Działa to świetnie, jednak aktualizacja do kątowej 9 wydaje się go zabijać. Czy ktoś wie dlaczego?
ymerej

1

Zwykle musisz zrezygnować z subskrypcji, gdy komponenty zostaną zniszczone, ale Angular będzie sobie z tym radził coraz częściej, na przykład w nowej, mniejszej wersji Angular4, mają one sekcję do anulowania subskrypcji:

Czy chcesz zrezygnować z subskrypcji?

Jak opisano w ActivatedRoute: punkt kompleksowej obsługi informacji o trasie na stronie Trasa i nawigacja, Router zarządza obserwowanymi przez siebie obiektami i lokalizuje subskrypcje. Subskrypcje są czyszczone po zniszczeniu komponentu, chroniąc przed wyciekiem pamięci, więc nie trzeba rezygnować z subskrypcji trasy obserwowalnej.

Również poniższy przykład jest dobrym przykładem Angulara do tworzenia komponentu i niszczenia go po, zobacz, jak komponent implementuje OnDestroy, jeśli potrzebujesz onInit, możesz również zaimplementować go w komponencie, podobnie jak implementacje 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();
  }
}

3
Zmieszany. Co tu mówisz Ty (ostatnie dokumenty / notatki Angulara) zdaje się mówić, że Angular zajmuje się tym, a potem potwierdza, że ​​rezygnacja z subskrypcji jest dobrym wzorcem. Dzięki.
jamie

1

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 wzrost wydajności w kilku przypadkach. Przypadki, takie jak komponenty, w których nie ma już subskrybowanych danych / zdarzeń lub wymagana jest nowa subskrypcja całkowicie nowego strumienia (odświeżanie itp.), Jest dobrym przykładem rezygnacji z subskrypcji.

0

w aplikacji SPA w funkcji ngOnDestroy (kątowy cykl życia) Dla każdej subskrypcji należy ją anulować . korzyść =>, aby zapobiec zbyt dużemu stanowi.

na przykład: w komponencie 1:

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

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

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

czynny:

import {BehaviorSubject} from 'rxjs/BehaviorSubject';

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

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

0

Do obsługi subskrypcji używam klasy „Unsubscriber”.

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

I możesz użyć tej klasy w dowolnym komponencie / usłudze / efekcie itp.

Przykład:

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

    this.addSubscription(subscription);
}

0

Możesz użyć najnowszej Subscriptionklasy, aby wypisać się z listy Obserwowalnych z niezbyt brudnym kodem.

Możemy to zrobić, normal variableale będzie to override the last subscriptionprzy każdym nowym abonamencie, więc unikaj tego, a to podejście jest bardzo przydatne, gdy masz do czynienia z większą liczbą Obseravable, i typem Obeservable, takim jak BehavoiurSubjectiSubject

Subskrypcja

Reprezentuje zasoby jednorazowe, takie jak wykonanie Observable. Subskrypcja ma jedną ważną metodę anulowania subskrypcji, która nie wymaga argumentów i po prostu usuwa zasoby posiadane przez subskrypcję.

możesz użyć tego na dwa sposoby,

  • możesz bezpośrednio przekazać subskrypcję do tablicy subskrypcji

     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());
      }
    
  • korzystanie add()zSubscription

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

SubscriptionMoże posiadać subskrypcje potomnych i bezpiecznie je wszystkie wypisać. Ta metoda obsługuje możliwe błędy (np. Jeśli dowolna subskrypcja podrzędna ma wartość NULL).

Mam nadzieję że to pomoże.. :)


0

Pakiet SubSink, łatwe i spójne rozwiązanie do rezygnacji z subskrypcji

Jak nikt inny o tym nie wspomniał, chcę polecić pakiet Subsink stworzony przez Ward Bell: https://github.com/wardbell/subsink#readme .

Używałem go w projekcie, gdy wszyscy jesteśmy programistami. Bardzo pomaga mieć spójny sposób działania w każdej sytuacji.


0

W przypadku obserwowalnych, które wypełniają się bezpośrednio po wyemitowaniu wyniku, takich jak AsyncSubjectnp. Obserwowalne z żądań HTTP i takie nie trzeba anulować subskrypcji. Wzywanie do nich nie zaszkodzi unsubscribe(), ale jeśli można to zaobserwować, closedmetoda rezygnacji z subskrypcji po prostu nic nie zrobi :

if (this.closed) {
  return;
}

Jeśli masz długotrwałe obserwowalne, które emitują kilka wartości w czasie (np. A BehaviorSubjectlub a ReplaySubject), musisz anulować subskrypcję, aby zapobiec wyciekom pamięci.

Możesz łatwo stworzyć obserwowalne, które kończy się bezpośrednio po wyemitowaniu wyniku z tak długowiecznych obserwowalnych za pomocą operatora potoku. W niektórych odpowiedziach take(1)wspomniana jest rura. Ale wolę się first()rury . Różnica take(1)polega na tym, że:

dostarczać EmptyErrordo wywołania zwrotnego błędu Obserwatora, jeśli Obserwowalny zakończy się przed wysłaniem kolejnego powiadomienia.

Kolejną zaletą pierwszego potoku jest to, że możesz przekazać predykat, który pomoże ci zwrócić pierwszą wartość, która spełnia określone kryteria:

const predicate = (result: any) => { 
  // check value and return true if it is the result that satisfies your needs
  return true;
}
observable.pipe(first(predicate)).subscribe(observer);

Pierwszy zakończy się bezpośrednio po emisji pierwszej wartości (lub po przekazaniu argumentu funkcji pierwszej wartości, która spełnia predykat), więc nie ma potrzeby anulowania subskrypcji.

Czasami nie masz pewności, czy możesz obserwować długo, czy nie. Nie twierdzę, że jest to dobra praktyka, ale zawsze możesz dodać firstrurkę, aby upewnić się, że nie będziesz musiał ręcznie anulować subskrypcji. Dodanie dodatkowej firstrury na obserwowalnym, która wyemituje tylko jedną wartość, nie zaszkodzi.

Podczas rozwoju można użyć do singlerury , która będzie zawodzić, jeśli źródłowych obserwowalnych emituje kilku zdarzeń. Może to pomóc w zbadaniu rodzaju obserwowalnego i tego, czy konieczne jest zrezygnowanie z niego, czy nie.

observable.pipe(single()).subscribe(observer);

firstI singlewydają się bardzo podobne, oba przewody mogą opcjonalnego predykatu ale różnice są ważne i ładnie podsumowane w tej stackoverflow odpowiedź tutaj :

Pierwszy

Wyemituje, gdy tylko pojawi się pierwszy element. Zakończy się zaraz po tym.

Pojedynczy

Nie powiedzie się, jeśli obserwowalne źródło wyemituje kilka zdarzeń.


Uwaga: W mojej odpowiedzi starałem się być jak najbardziej dokładny i kompletny z odniesieniami do oficjalnej dokumentacji, ale proszę o komentarz, jeśli brakuje czegoś ważnego ...


-1

--- Zaktualizuj rozwiązanie Angular 9 i Rxjs 6

  1. Wykorzystanie unsubscribew ngDestroycyklu życia elementu kątowego
class SampleComponent implements OnInit, OnDestroy {
  private subscriptions: Subscription;
  private sampleObservable$: Observable<any>;

  constructor () {}

  ngOnInit(){
    this.subscriptions = this.sampleObservable$.subscribe( ... );
  }

  ngOnDestroy() {
    this.subscriptions.unsubscribe();
  }
}
  1. Używanie takeUntilw Rxjs
class SampleComponent implements OnInit, OnDestroy {
  private unsubscribe$: new Subject<void>;
  private sampleObservable$: Observable<any>;

  constructor () {}

  ngOnInit(){
    this.subscriptions = this.sampleObservable$
    .pipe(takeUntil(this.unsubscribe$))
    .subscribe( ... );
  }

  ngOnDestroy() {
    this.unsubscribe$.next();
    this.unsubscribe$.complete();
  }
}
  1. dla niektórych akcji, które wywołujesz ngOnInit, zdarzają się tylko raz, gdy inicjuje się komponent.
class SampleComponent implements OnInit {

  private sampleObservable$: Observable<any>;

  constructor () {}

  ngOnInit(){
    this.subscriptions = this.sampleObservable$
    .pipe(take(1))
    .subscribe( ... );
  }
}

Mamy też asyncfajkę. Ale to użycie na szablonie (nie w komponencie Angular).


Twój pierwszy przykład jest niekompletny.
Paul-Sebastian Manole
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.