Quando devo armazenar as Subscription
instâncias e chamar unsubscribe()
durante o ciclo de vida do NgOnDestroy e quando posso simplesmente ignorá-las?
Salvar todas as assinaturas introduz muita confusão no código do componente.
O Guia do Cliente HTTP ignora assinaturas como esta:
getHeroes() {
this.heroService.getHeroes()
.subscribe(
heroes => this.heroes = heroes,
error => this.errorMessage = <any>error);
}
Ao mesmo tempo, o Guia de rotas e navegação diz que:
Eventualmente, navegaremos para outro lugar. O roteador removerá esse componente do DOM e o destruirá. Precisamos nos limpar antes que isso aconteça. Especificamente, devemos cancelar a inscrição antes que o Angular destrua o componente. Não fazer isso pode criar um vazamento de memória.
Nós desinscrever de nossa
Observable
nongOnDestroy
método.
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();
}
angular
rxjs
observable
subscription
angular-component-life-cycle
Sergey Tihon
fonte
fonte
Subscription
quehttp-requests
pode ser ignorado, pois eles ligam apenasonNext
uma vez e depois ligamonComplete
. EmRouter
vez disso, chamaonNext
repetidamente e pode nunca ligaronComplete
(não tem certeza disso ...). O mesmo vale paraObservable
s deEvent
s. Então acho que deveriam serunsubscribed
.subscribe
si mesma que potencialmente aloca recursos a montante.Respostas:
--- Editar 4 - Recursos adicionais (01/09/2018)
Em um episódio recente de Adventures in Angular, Ben Lesh e Ward Bell discutem as questões sobre como / quando cancelar a inscrição em um componente. A discussão começa por volta das 1:05:30.
Ward menciona
right now there's an awful takeUntil dance that takes a lot of machinery
e Shai Reznik mencionaAngular handles some of the subscriptions like http and routing
.Em resposta, Ben menciona que há discussões no momento para permitir que os Observables se conectem aos eventos do ciclo de vida do componente Angular e Ward sugere um Observável dos eventos do ciclo de vida que um componente pode assinar como uma maneira de saber quando concluir os Observables mantidos como estado interno do componente.
Dito isto, precisamos principalmente de soluções agora, então aqui estão alguns outros recursos.
Uma recomendação para o
takeUntil()
padrão do membro da equipe principal do RxJs, Nicholas Jamieson, e uma regra tslint para ajudar a aplicá-lo. https://ncjamieson.com/avoiding-takeuntil-leaks/Pacote npm leve que expõe um operador Observable que usa uma instância de componente (
this
) como parâmetro e cancela automaticamente a inscrição durantengOnDestroy
. https://github.com/NetanelBasal/ngx-take-until-destroyOutra variação do exposto acima com ergonomia um pouco melhor se você não estiver executando o AOT (mas todos nós deveríamos estar executando o AOT agora). https://github.com/smnbbrv/ngx-rx-collector
Diretiva personalizada
*ngSubscribe
que funciona como canal assíncrono, mas cria uma exibição incorporada no seu modelo para que você possa consultar o valor 'desembrulhado' em todo o modelo. https://netbasal.com/diy-subscription-handling-directive-in-angular-c8f6e762697fMenciono em um comentário no blog de Nicholas que o uso excessivo de
takeUntil()
pode ser um sinal de que seu componente está tentando fazer muito e que a separação dos componentes existentes em componentes de recursos e de apresentação deve ser considerada. Você pode então| async
observar o componente Observável do componenteInput
Apresentacional, o que significa que nenhuma assinatura é necessária em nenhum lugar. Leia mais sobre essa abordagem aqui--- Editar 3 - A solução 'oficial' (09/04/2017)
Conversei com Ward Bell sobre esta questão na NGConf (eu até lhe mostrei a resposta que ele dizia estar correta), mas ele me disse que a equipe de documentos da Angular tinha uma solução para essa pergunta que não é publicada (embora eles estejam trabalhando para aprová-la) ) Ele também me disse que eu poderia atualizar minha resposta do SO com a próxima recomendação oficial.
A solução que todos devemos usar daqui para frente é adicionar um
private ngUnsubscribe = new Subject();
campo a todos os componentes que possuem.subscribe()
chamadas paraObservable
s dentro do código de classe.Então chamamos
this.ngUnsubscribe.next(); this.ngUnsubscribe.complete();
nossosngOnDestroy()
métodos.O segredo (como já observado pelo @metamaker ) é ligar
takeUntil(this.ngUnsubscribe)
antes de cada uma das nossas.subscribe()
chamadas, o que garantirá que todas as assinaturas serão limpas quando o componente for destruído.Exemplo:
Nota: É importante adicionar o
takeUntil
operador como o último para evitar vazamentos com observáveis intermediários na cadeia do operador.--- Editar 2 (28/12/2016)
Fonte 5
O tutorial Angular, o capítulo Roteamento, agora declara o seguinte: "O roteador gerencia os observáveis que fornece e localiza as assinaturas. As assinaturas são limpas quando o componente é destruído, protegendo contra vazamentos de memória, para que não seja necessário cancelar a assinatura de a rota params Observable. " - Mark Rajcok
Aqui está uma discussão sobre as questões do Github para os documentos angulares sobre roteadores observáveis, onde Ward Bell menciona que há esclarecimentos para tudo isso.
--- Editar 1
Fonte 4
Neste vídeo do NgEurope, Rob Wormald também diz que não é necessário cancelar a assinatura do Router Observables. Ele também menciona o
http
serviço eActivatedRoute.params
, neste vídeo, de novembro de 2016 .--- Resposta original
TLDR:
Para esta pergunta, existem (2) tipos de
Observables
- valor finito e valor infinito .http
Observables
produza valores finitos (1) e algo como um DOMevent listener
Observables
produz valores infinitos .Se você chamar manualmente
subscribe
(sem usar tubulação assíncrona), em seguida,unsubscribe
a partir infinitoObservables
.Não se preocupe com os finitos ,
RxJs
cuidará deles.Fonte 1
Encontrei uma resposta de Rob Wormald em Gitter da Angular aqui .
Ele afirma (eu reorganizei para maior clareza e ênfase é minha)
Ele também menciona neste vídeo do YouTube sobre Observables que
they clean up after themselves
... no contexto dos Observables quecomplete
(como Promises, que sempre são concluídas porque sempre produzem 1 valor e terminam - nunca nos preocupamos em cancelar a inscrição da Promises para garantir que eles limpem oxhr
evento ouvintes, certo?).Fonte 2
Também no guia Rangle para Angular 2, ele lê
Quando a frase se
our Observable has a longer lifespan than our subscription
aplica?Aplica-se quando uma assinatura é criada dentro de um componente que é destruído antes (ou não 'muito' antes) da
Observable
conclusão.Eu li isso como significando que se subscrevermos uma
http
solicitação ou um observável que emita 10 valores e nosso componente for destruído antes que ahttp
solicitação retorne ou que os 10 valores tenham sido emitidos, ainda estamos ok!Quando a solicitação retornar ou o décimo valor finalmente for emitido, a
Observable
conclusão será concluída e todos os recursos serão limpos.Fonte 3
Se olharmos para este exemplo a partir do mesmo guia Rangle, podemos ver que o
Subscription
toroute.params
requer umunsubscribe()
porque não sabemos quando elesparams
irão parar de mudar (emitindo novos valores).O componente pode ser destruído navegando para longe; nesse caso, os parâmetros de rota provavelmente ainda estarão mudando (eles podem tecnicamente mudar até o aplicativo terminar) e os recursos alocados na assinatura ainda serão alocados porque não houve a
completion
.fonte
complete()
por si só não parece limpar as assinaturas. No entanto, chamandonext()
e depois ocomplete()
faz, acredito quetakeUntil()
só para quando um valor é produzido, não quando a sequência termina.Subject
dentro de um componente e alternando-ongIf
para acionarngOnInit
engOnDestroy
mostrar que o sujeito e suas assinaturas nunca serão concluídas ou descartadas (finally
vinculado um operador à assinatura). Devo chamarSubject.complete()
emngOnDestroy
, por isso, as inscrições podem limpar depois de si.takeUnitl
abordagem, nunca precisaremos cancelar manualmente a inscrição de quaisquer observáveis? É esse o caso? Além disso, por que precisamos ligarnext()
parangOnDestroy
, por que não apenas ligarcomplete()
?Você não precisa ter várias assinaturas e cancelar a assinatura manualmente. Use a combinação Subject e takeUntil para lidar com assinaturas como um chefe:
A abordagem alternativa , proposta por @acumartini nos comentários , usa takeWhile em vez de takeUntil . Você pode preferir, mas lembre-se de que dessa maneira sua execução Observable não será cancelada no ngDestroy do seu componente (por exemplo, quando você faz cálculos demorados ou espera os dados do servidor). O método, baseado em takeUntil , não tem essa desvantagem e leva ao cancelamento imediato da solicitação. Agradecemos a @AlexChe pela explicação detalhada nos comentários .
Então aqui está o código:
fonte
takeUntil
etakeWhile
. O primeiro cancela a assinatura da fonte observável imediatamente quando acionado, enquanto o último cancela a assinatura apenas assim que o próximo valor é produzido pela fonte observável. Se produzir um valor pela fonte observável é uma operação que consome recursos, a escolha entre as duas pode ir além da preferência de estilo. Veja o coragemtakeUntil
vstakeWhile
, no entanto, não para o nosso caso específico. Quando precisamos cancelar a inscrição de ouvintes na destruição de componentes , estamos apenas checando o valor booleano como() => alive
emtakeWhile
, para que qualquer operação que consome tempo / memória não seja usada e a diferença seja basicamente sobre estilo (ofc, nesse caso específico).Observable
, que extrai internamente algumas moedas criptográficas e dispara umnext
evento para cada moeda extraída, e a mineração de uma moeda leva um dia. ComtakeUntil
o cancelamento da assinatura, a mineração de origem seráObservable
imediatamentengOnDestroy
chamada uma vez durante a destruição de nossos componentes. Assim, aObservable
função de mineração pode cancelar sua operação imediatamente durante esse processo.takeWhile
, nongOnDestory
apenas definimos a variável booleana. Mas aObservable
função de mineração ainda pode funcionar por até um dia e, somente então, durante anext
chamada, ela perceberá que não há assinaturas ativas e precisará cancelar.A classe Subscription possui um recurso interessante:
Você pode criar um objeto de Inscrição agregado que agrupe todas as suas assinaturas. Você faz isso criando uma assinatura vazia e adicionando assinaturas a ela usando seu
add()
método. Quando seu componente é destruído, você só precisa cancelar a assinatura agregada.fonte
takeUntil
abordagem oficial versus essa abordagem de coleta de assinaturas e chamadasunsubscribe
. (Esta abordagem parece muito mais limpo para mim.)this.subscriptions
é nulosub = subsciption.add(..).add(..)
porque, em muitos casos, ele produz resultados inesperados github.com/ReactiveX/rxjs/issues/2769#issuecomment-345636477Algumas das práticas recomendadas para cancelar a assinatura de observáveis dentro dos componentes Angular:
Uma citação de
Routing & Navigation
E ao responder aos seguintes links:
http
observávelReuni algumas das práticas recomendadas para cancelar a assinatura de observáveis dentro dos componentes Angular para compartilhar com você:
http
O cancelamento da inscrição observável é condicional e devemos considerar os efeitos do 'retorno de chamada de assinatura' sendo executado depois que o componente é destruído caso a caso. Sabemos que o angular cancela e limpa ohttp
próprio observável (1) , (2) . Embora isso seja verdade da perspectiva dos recursos, ele conta apenas metade da história. Digamos que estamos falando sobre chamar diretamentehttp
de dentro de um componente e ohttp
resposta levou mais tempo do que o necessário para que o usuário fechasse o componente. osubscribe()
manipulador ainda será chamado, mesmo que o componente seja fechado e destruído. Isso pode ter efeitos colaterais indesejados e, nos piores cenários, deixar o estado do aplicativo quebrado. Também pode causar exceções se o código no retorno de chamada tentar chamar algo que acabou de ser descartado. No entanto, ao mesmo tempo, ocasionalmente, eles são desejados. Por exemplo, digamos que você esteja criando um cliente de e-mail e acionando um som quando o e-mail terminar de enviar.)AsyncPipe
o máximo possível, pois ele cancela automaticamente a assinatura do observado na destruição de componentes.ActivatedRoute
observáveis, comoroute.params
se eles estivessem inscritos em um componente aninhado (Adicionado dentro da tpl com o seletor de componente) ou componente dinâmico, pois eles podem ser inscritos muitas vezes, desde que o componente pai / host exista. Não é necessário cancelar a assinatura deles em outros cenários, como mencionado na citação acima, deRouting & Navigation
documentos.Nota: Em relação aos serviços com escopo definido, ou seja, provedores de componentes, eles são destruídos quando o componente é destruído. Nesse caso, se subscrevermos qualquer elemento observável dentro deste provedor, considere cancelar a inscrição usando o
OnDestroy
gancho do ciclo de vida que será chamado quando o serviço for destruído, de acordo com os documentos.takeUntil
(3) ou usar estenpm
pacote mencionado em (4) A maneira mais fácil de cancelar a assinatura de Observables in Angular .FormGroup
observáveis comoform.valueChanges
eform.statusChanges
Renderer2
serviço comorenderer2.listen
HostListener
o angular se preocupa em remover os ouvintes de eventos, se necessário, e evita qualquer possível vazamento de memória devido a ligações de eventos.Uma boa dica final : se você não sabe se um observável está sendo cancelado / concluído automaticamente ou não, adicione um
complete
retorno de chamadasubscribe(...)
e verifique se ele é chamado quando o componente é destruído.fonte
ngOnDestroy
são chamados quando o serviço é fornecido em um nível diferente do nível raiz, por exemplo, fornecido explicitamente em um componente que é removido posteriormente. Nestes casos, você deve cancelar a inscrição dos serviços observáveis internasngOnDestroy
, que é sempre chamada quando os serviços são destruídos github.com/angular/angular/commit/…Feel free to unsubscribe anyway. It is harmless and never a bad practice.
e em relação à sua pergunta, depende. Se o componente filho for iniciado várias vezes (por exemplo, adicionado dentrongIf
ou sendo carregado dinamicamente), você deverá cancelar a inscrição para evitar adicionar várias assinaturas ao mesmo observador. Caso contrário, não há necessidade. Mas eu prefiro cancelar a inscrição dentro do componente filho, pois isso o torna mais reutilizável e isolado de como ele poderia ser usado.Depende. Se ligando
someObservable.subscribe()
, você começa a reter algum recurso que deve ser liberado manualmente quando o ciclo de vida do seu componente terminar, então você deve ligartheSubscription.unsubscribe()
para evitar vazamento de memória.Vamos dar uma olhada em seus exemplos:
getHero()
retorna o resultado dehttp.get()
. Se você olhar para o código-fonte angular 2 ,http.get()
cria dois ouvintes de evento:e ligando
unsubscribe()
, você pode cancelar a solicitação e os ouvintes:Observe que
_xhr
é específico da plataforma, mas acho que é seguro assumir que é umXMLHttpRequest()
no seu caso.Normalmente, isso é evidência suficiente para justificar uma
unsubscribe()
chamada manual . Porém, de acordo com esta especificação do WHATWG , o itemXMLHttpRequest()
está sujeito à coleta de lixo assim que é "concluída", mesmo se houver ouvintes de eventos anexados a ele. Acho que é por isso que o guia oficial angular 2 omiteunsubscribe()
e permite que o GC limpe os ouvintes.Quanto ao seu segundo exemplo, isso depende da implementação de
params
. A partir de hoje, o guia oficial angular não mostra mais o cancelamento da inscriçãoparams
. Eu olhei para o src novamente e descobri queparams
é apenas um BehaviorSubject . Como nenhum ouvinte ou timer de evento foi usado e nenhuma variável global foi criada, deve ser seguro omitirunsubscribe()
.O ponto principal da sua pergunta é que sempre chame
unsubscribe()
como proteção contra vazamento de memória, a menos que você tenha certeza de que a execução do observável não cria variáveis globais, adiciona ouvintes de eventos, configura temporizadores ou faz qualquer outra coisa que resulte em vazamentos de memória .Em caso de dúvida, examine a implementação desse observável. Se o observável tiver escrito alguma lógica de limpeza na sua
unsubscribe()
, que geralmente é a função retornada pelo construtor, você tem bons motivos para considerar seriamente a chamadaunsubscribe()
.fonte
A documentação oficial do Angular 2 fornece uma explicação para quando cancelar a inscrição e quando pode ser ignorada com segurança. Dê uma olhada neste link:
https://angular.io/docs/ts/latest/cookbook/component-communication.html#!#bidirectional-service
Procure o parágrafo com o título Pais e filhos se comunicam por meio de um serviço e, em seguida, a caixa azul:
Espero que isso ajude você.
fonte
Baseado em: Usando a herança de classe para ligar ao ciclo de vida do componente Angular 2
Outra abordagem genérica:
E use :
fonte
this.componentDestroyed$.next()
chamada como a solução aceita por sean acima ...A resposta oficial do Edit # 3 (e variações) funciona bem, mas o que me impressiona é a "confusão" da lógica de negócios em torno da assinatura observável.
Aqui está outra abordagem usando wrappers.
O arquivo subscribeAndGuard.ts é usado para criar uma nova extensão Observable para quebrar
.subscribe()
e dentro dela para quebrarngOnDestroy()
.O uso é o mesmo que
.subscribe()
, exceto por um primeiro parâmetro adicional que faz referência ao componente.Aqui está um componente com duas assinaturas, uma com o wrapper e outra sem. A única ressalva é que ele deve implementar o OnDestroy (com corpo vazio, se desejado), caso contrário, o Angular não sabe chamar a versão agrupada .
Um demo plunker está aqui
Uma observação adicional: Re Edit 3 - A solução 'Oficial', isso pode ser simplificado usando takeWhile () em vez de takeUntil () antes das assinaturas, e um booleano simples em vez de outro Observable no ngOnDestroy.
fonte
Como a solução de seangwright (Edição 3) parece ser muito útil, também achei difícil incluir esse recurso no componente base e sugerir que outros colegas de equipe do projeto se lembrem de chamar super () no ngOnDestroy para ativar esse recurso.
Esta resposta fornece uma maneira de liberar-se da chamada super e tornar "componentDestroyed $" um núcleo do componente base.
E então você pode usar esse recurso livremente, por exemplo:
fonte
Seguindo a resposta de @seangwright , escrevi uma classe abstrata que lida com assinaturas "infinitas" de observáveis em componentes:
Para usá-lo, apenas estenda-o em seu componente angular e chame o
subscribe()
método da seguinte maneira:Ele também aceita o erro e realiza retornos de chamada como de costume, um objeto observador ou nenhum retorno de chamada. Lembre-
super.ngOnDestroy()
se de ligar se você também estiver implementando esse método no componente filho.Encontre aqui uma referência adicional de Ben Lesh: RxJS: Não cancele a inscrição .
fonte
Eu tentei a solução de seangwright (Editar 3)
Isso não está funcionando para o Observable criado por timer ou intervalo.
No entanto, consegui trabalhar usando outra abordagem:
fonte
Gosto das duas últimas respostas, mas tive um problema se a subclasse mencionada
"this"
emngOnDestroy
.Eu o modifiquei para ser isso e parece que resolveu esse problema.
fonte
this.ngOnDestroy = () => { f.bind(this)(); this.componentDestroyed$.complete(); };
Caso seja necessário cancelar a assinatura, o operador a seguir para o método de tubo observável pode ser usado
pode ser usado assim:
O operador envolve o método ngOnDestroy do componente.
Importante: o operador deve ser o último em tubo observável.
fonte
Você geralmente precisa cancelar a assinatura quando os componentes forem destruídos, mas o Angular vai lidar com isso cada vez mais, como, por exemplo, na nova versão secundária do Angular4, eles têm esta seção para rotear o cancelamento da inscrição:
Além disso, o exemplo abaixo é um bom exemplo da Angular para criar um componente e destruí-lo depois. Veja como o componente implementa o OnDestroy. Se você precisar do OnInit, também pode implementá-lo no seu componente, como implementos
OnInit, OnDestroy
fonte
Outra pequena adição às situações acima mencionadas é:
fonte
no aplicativo SPA na função ngOnDestroy (angular lifeCycle) Para cada assinatura, é necessário cancelar a assinatura . vantagem => para impedir que o estado fique muito pesado.
por exemplo: no componente1:
em serviço:
no componente2:
fonte
Para lidar com a assinatura, uso uma classe "Unsubscriber".
Aqui está a classe Unsubscriber.
E você pode usar esta classe em qualquer componente / Serviço / Efeito etc.
Exemplo:
fonte
Você pode usar a
Subscription
classe mais recente para cancelar a inscrição no Observable com código não tão confuso.Podemos fazer isso com,
normal variable
mas seráoverride the last subscription
em cada nova assinatura. Portanto, evite isso, e essa abordagem é muito útil quando você está lidando com um número maior de Obseravables e tipos de Obeservables comoBehavoiurSubject
eSubject
Inscrição
você pode usar isso de duas maneiras,
você pode enviar diretamente a assinatura para o Subscription Array
usando
add()
deSubscription
UMA
Subscription
pode manter assinaturas filhas e cancelar todas elas com segurança. Este método lida com possíveis erros (por exemplo, se alguma assinatura filha for nula).Espero que isto ajude.. :)
fonte
O pacote SubSink, uma solução fácil e consistente para cancelar a inscrição
Como ninguém mais o mencionou, quero recomendar o pacote Subsink criado por Ward Bell: https://github.com/wardbell/subsink#readme .
Eu tenho usado em um projeto onde somos vários desenvolvedores. Ajuda muito ter uma maneira consistente que funcione em todas as situações.
fonte
Para observáveis que são concluídos diretamente após a emissão do resultado, como,
AsyncSubject
por exemplo, observáveis de solicitações de HTTP, você não precisa cancelar a inscrição. Não custa chamarunsubscribe()
, mas se o observável éclosed
o método de cancelamento da assinatura simplesmente não fará nada :Quando você tem observáveis de longa duração que emitem vários valores ao longo do tempo (como por exemplo, a
BehaviorSubject
ou aReplaySubject
), é necessário cancelar a assinatura para evitar vazamentos de memória.Você pode criar facilmente um observável que é concluído diretamente após emitir um resultado de observáveis de longa duração usando um operador de tubo. Em algumas respostas aqui o
take(1)
tubo é mencionado. Mas eu prefiro ofirst()
cachimbo . A diferençatake(1)
é que ele irá:Outra vantagem do primeiro canal é que você pode passar um predicado que o ajudará a retornar o primeiro valor que atenda a certos critérios:
O First será concluído diretamente após a emissão do primeiro valor (ou ao passar um argumento de função, o primeiro valor que satisfaça seu predicado), para que não seja necessário cancelar a inscrição.
Às vezes, você não tem certeza se tem uma vida longa observável ou não. Não estou dizendo que é uma boa prática, mas você pode sempre adicionar o
first
pipe apenas para garantir que não precisará cancelar manualmente a inscrição. Adicionar umfirst
tubo adicional a um observável que emitirá apenas um valor não prejudica.Durante o desenvolvimento, você pode usar o
single
canal que falhará se a origem observável emitir vários eventos. Isso pode ajudá-lo a explorar o tipo de observável e se é necessário cancelar a inscrição ou não.Os dois canais
first
esingle
parecem muito semelhantes, podem ter um predicado opcional, mas as diferenças são importantes e bem resumidas nesta resposta do stackoverflow aqui :Observe que tentei ser o mais preciso e completo possível na minha resposta com referências à documentação oficial, mas comente se algo importante estiver faltando ...
fonte
--- Atualize a solução Angular 9 e Rxjs 6
unsubscribe
nongDestroy
ciclo de vida do componente angulartakeUntil
em RxjsngOnInit
isso acontece apenas uma vez quando o componente init.Nós também temos
async
cachimbo. Mas, este uso no modelo (não no componente Angular).fonte