--- Modifica 4 - Risorse aggiuntive (2018/09/01)
In un recente episodio di Adventures in Angular, Ben Lesh e Ward Bell discutono dei problemi su come / quando annullare l'iscrizione a un componente. La discussione inizia alle 1:05:30 circa.
Menzioni di rione right now there's an awful takeUntil dance that takes a lot of machinery
e menzioni di Shai Reznik Angular handles some of the subscriptions like http and routing
.
In risposta Ben menziona che in questo momento ci sono discussioni per consentire a Observables di agganciarsi agli eventi del ciclo di vita dei componenti angolari e Ward suggerisce un osservabile di eventi del ciclo di vita a cui un componente potrebbe iscriversi come un modo per sapere quando completare gli osservabili mantenuti come stato interno del componente.
Detto questo, ora abbiamo principalmente bisogno di soluzioni, quindi ecco alcune altre risorse.
Una raccomandazione per lo takeUntil()
schema da parte del membro del team principale di RxJ Nicholas Jamieson e una regola tslint per aiutare a farla rispettare. https://ncjamieson.com/avoiding-takeuntil-leaks/
Pacchetto npm leggero che espone un operatore Observable che accetta un'istanza del componente ( this
) come parametro e annulla automaticamente l'iscrizione durante ngOnDestroy
.
https://github.com/NetanelBasal/ngx-take-until-destroy
Un'altra variante di quanto sopra con un'ergonomia leggermente migliore se non stai eseguendo build AOT (ma dovremmo tutti fare AOT ora).
https://github.com/smnbbrv/ngx-rx-collector
Direttiva personalizzata *ngSubscribe
che funziona come una pipe asincrona ma crea una vista incorporata nel modello in modo da poter fare riferimento al valore "non imballato" in tutto il modello.
https://netbasal.com/diy-subscription-handling-directive-in-angular-c8f6e762697f
Cito in un commento al blog di Nicholas che un uso eccessivo di takeUntil()
potrebbe essere un segno che il componente sta provando a fare troppo e che occorre considerare la separazione dei componenti esistenti in componenti Feature e Presentational . È quindi possibile | async
osservare l'Osservabile dal componente Feature in uno Input
del componente Presentational, il che significa che non sono necessari abbonamenti ovunque. Leggi di più su questo approccio qui
--- Modifica 3 - La soluzione "ufficiale" (2017/04/09)
Ho parlato con Ward Bell di questa domanda a NGConf (gli ho anche mostrato questa risposta che ha detto che era corretta) ma mi ha detto che il team di docenti di Angular aveva una soluzione a questa domanda che non è stata pubblicata (anche se stanno lavorando per ottenere l'approvazione ). Mi disse anche che avrei potuto aggiornare la mia risposta SO con l'imminente raccomandazione ufficiale.
La soluzione che dovremmo usare in futuro è quella di aggiungere un private ngUnsubscribe = new Subject();
campo a tutti i componenti che hanno .subscribe()
chiamate a Observable
all'interno del loro codice di classe.
Chiamiamo quindi i this.ngUnsubscribe.next(); this.ngUnsubscribe.complete();
nostri ngOnDestroy()
metodi.
La salsa segreta (come già notato da @metamaker ) è quella di chiamare takeUntil(this.ngUnsubscribe)
prima di ciascuna delle nostre .subscribe()
chiamate che garantirà che tutti gli abbonamenti vengano ripuliti quando il componente viene distrutto.
Esempio:
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();
}
}
Nota: è importante aggiungere l' takeUntil
operatore come ultimo per evitare perdite con osservabili intermedi nella catena dell'operatore.
--- Modifica 2 (28/12/2016)
Fonte 5
Il tutorial di Angular, il capitolo Routing ora afferma quanto segue: "Il router gestisce gli osservabili che fornisce e localizza gli abbonamenti. Gli abbonamenti vengono ripuliti quando il componente viene distrutto, proteggendo da perdite di memoria, quindi non è necessario annullare l'iscrizione a il percorso è osservabile. " - Mark Rajcok
Ecco una discussione sulle questioni di Github per i documenti angolari riguardanti gli osservabili del router in cui Ward Bell menziona che è in corso un chiarimento per tutto ciò.
--- Modifica 1
Fonte 4
In questo video di NgEurope Rob Wormald afferma anche che non è necessario annullare l'iscrizione a Router Observables. Cita anche il http
servizio e ActivatedRoute.params
in questo video di novembre 2016 .
--- Risposta originale
TLDR:
Per questa domanda ci sono (2) tipi di Observables
- valore finito e valore infinito .
http
Observables
produce valori finiti (1) e qualcosa come un DOM event listener
Observables
produce valori infiniti .
Se chiamate manualmente subscribe
(non usando la pipa asincrona), quindi unsubscribe
da infinito Observables
.
Non preoccuparti di quelli finiti , RxJs
si prenderanno cura di loro.
Fonte 1
Ho rintracciato una risposta di Rob Wormald in Angular's Gitter qui .
Egli afferma (ho riorganizzato per chiarezza e l'enfasi è mia)
se si tratta di una sequenza a valore singolo (come una richiesta http) la pulizia manuale non è necessaria (supponendo che abbiate sottoscritto manualmente il controller)
dovrei dire "se è una sequenza che completa " (di cui le sequenze a valore singolo, alla http, sono una)
se è una sequenza infinita , dovresti annullare l' iscrizione della pipe asincrona che fa per te
Inoltre menziona in questo video di YouTube su Observables che they clean up after themselves
... nel contesto di Observables che complete
(come Promises, che completano sempre perché producono sempre 1 valore e terminano - non ci siamo mai preoccupati di annullare l'iscrizione a Promises per assicurarci che ripulissero l' xhr
evento ascoltatori, vero?).
Fonte 2
Anche nella guida Rangle a Angular 2 si legge
Nella maggior parte dei casi non avremo bisogno di chiamare esplicitamente il metodo di annullamento dell'iscrizione a meno che non desideriamo annullare anticipatamente o se il nostro osservabile ha una durata maggiore rispetto al nostro abbonamento. Il comportamento predefinito degli operatori osservabili è di disporre dell'abbonamento non appena vengono pubblicati i messaggi .complete () o .error (). Tieni presente che RxJS è stato progettato per essere utilizzato in modo "incendi e dimentica" per la maggior parte del tempo.
Quando si applica la frase our Observable has a longer lifespan than our subscription
?
Si applica quando viene creato un abbonamento all'interno di un componente che viene distrutto prima (o non "molto prima") del Observable
completamento.
Ho letto questo come significato se ci iscriviamo a una http
richiesta o a un osservabile che emette 10 valori e il nostro componente viene distrutto prima che la http
richiesta ritorni o che i 10 valori siano stati emessi, siamo ancora ok!
Quando la richiesta viene restituita o viene finalmente emesso il decimo valore, il processo Observable
verrà completato e tutte le risorse verranno ripulite.
Fonte 3
Se guardiamo questo esempio dalla stessa guida di Rangle possiamo vedere che il Subscription
to route.params
richiede un unsubscribe()
perché non sappiamo quando params
smetteranno di cambiare (emettendo nuovi valori).
Il componente potrebbe essere distrutto allontanandosi nel qual caso i parametri del percorso probabilmente continueranno a cambiare (potrebbero cambiare tecnicamente fino al termine dell'app) e le risorse allocate nell'abbonamento verrebbero comunque allocate perché non è presente un completion
.
Subscription
che sihttp-requests
possa ignorare, poiché chiamano soloonNext
una volta e poi chiamanoonComplete
. IlRouter
invece chiamaonNext
ripetutamente e potrebbe mai chiamareonComplete
(non sicuro di questo ...). Lo stesso vale perObservable
s daEvent
s. Quindi immagino che dovrebbero essereunsubscribed
.