Evitando problemas com Observables.
Tags: angular, javascript, webperf
Published: 2019-10-27 23:59:57

O  Reactive Extensions Library for JavaScript (RxJS) é uma biblioteca muito poderosa, isso é inegável. Não foi à toa que a equipe do Angular a integrou em seu framework. Mas com grandes poderes vem grandes responsabilidades dentre estas estão os *__Observables__*.

## Por que devo me preocupar com isso?

Fazendo uma analogia imagine que você se inscreveu em vários canais do youtube, mas alguns desses canais já não são mais do seu interesse. Enquanto você não anular a inscrição (``unsubscribe``), vai continuar recebendo as notificações desses canais.

Com *Observable* não é diferente.  Ao chamar o método ``subscribe()``, sua aplicação irá observar os eventos emitidos até que você diga que não tem mais interesse.

No exemplo abaixo usamos o operador ``interval`` que define um intervalo de tempo (passado como parâmetro) no qual o *Observable* irá emitir um evento.

Note que também vamos imprimir no console quando o componente for destruído, usando o *lifecycle hook* ``ngOnDestroy``.

![código do componente alpha](https://thepracticaldev.s3.amazonaws.com/i/pfy0piy9a0lnesntehoi.png)


Nossa aplicação de exemplo tem duas páginas, onde é exibido o componente **Alpha** em uma e o **Beta** em outra.

![Exemplo de problema com subscrições de Observables](https://thepracticaldev.s3.amazonaws.com/i/40jo0k7yu674cm0m6b6m.gif)


Mesmo depois do componente Alpha ter sido destruído, os eventos do nosso *Observable* continuaram a chegar :(

Isso pode gerar comportamentos inesperados, problemas de performance, problemas de gerenciamento de memória etc. 

## Cuidando dos Observables

Devemos anular a inscrição de nossos *Observables* e podemos fazer isso das seguintes maneiras:

### 1. Usando o ``unsubscribe()``

Quando nos inscrevemos em um *Observable* nos é retornado um objeto [Subscription](https://rxjs-dev.firebaseapp.com/guide/subscription), que por sua vez, representa um recurso descartável. Podemos guardar a referência desse objeto para chamar o ``unsubscribe()`` no momento oportuno, como no *lifecycle hook* ``ngOnDestroy`` por exemplo.

![Unsubscribe usando a referência do Subscription](https://thepracticaldev.s3.amazonaws.com/i/jt7etn9jfu6843e1ivxr.png)

Caso tenha mais de um *Observable*, podemos criar um ``array`` e guardar a referência das subscrições e assim como no exemplo anterior, no *lifecycle hook* ``ngOnDestroy`` chamar o ``unsubscribe()``, porém dessa vez num laço ``forEach``.

![Unsubscribe usando a referência de vários Subscription](https://thepracticaldev.s3.amazonaws.com/i/jkvmlwchh3cvp8damqxs.png)


### 2. Usando ``takeUntil``

Podemos também abusar mais do poder do RxJS e utilizar seus operadores para anular as inscrições, usando por exemplo o operador [``takeUntil``](https://rxjs-dev.firebaseapp.com/api/operators/takeUntil).

![Unsubscribe usando operador takeUntil](https://thepracticaldev.s3.amazonaws.com/i/db2xychfjrttdkzv3lwn.png)

Atenção ao usar essa técnica, pois caso tenha outro operador após o ``takeUntil``, como por exemplo operadores de transformação ou composição, pode ocorrer um *subscription leak* e não funcionar como esperado. [Nicholas Jamieson](https://blog.angularindepth.com/rxjs-avoiding-takeuntil-leaks-fb5182d047ef) explica esse problema com mais detalhes.
A dica é deixar esse operador por último sempre que possível.

### 3. Usando o ``AsyncPipe``

Também podemos deixar na conta do Angular, fazendo com que ele se encarregue da anulação da inscrição.

Para isso, não faremos a inscrição diretamente no *Observable*. Passaremos essa tarefa para o nosso *template* usando as diretivas ``*ngFor`` ou ``*ngIf`` em conjunto com o ``AsyncPipe``

![componente alpha](https://thepracticaldev.s3.amazonaws.com/i/c1d78ej83kcia06h558t.png)

Note que não chamamos o ``subscribe()`` dessa vez.

No *template*, atenção ao ``*ngIf="someDataAsObservable$ | async as i; else loading"``, ao usar o ``| async`` pedimos para o Angular se inscrever nesse *Observable* e entregar o resultado para a variável ``i`` assim que disponível.
Enquanto o valor não estiver disponível o ``if`` será avaliado como ``false`` e será exibido o conteúdo do nosso ``ng-template``, conforme indicado no ``else``.

![componente alpha template](https://thepracticaldev.s3.amazonaws.com/i/38kqw0djh5zqr9j1df8i.png)

Vamos ver como ficou:

![Unsubscribe com diretiva](https://thepracticaldev.s3.amazonaws.com/i/hbo58pnwmkd60lmzu3qn.gif)

Note que ao navegar para a página do componente Beta, paramos de ouvir os eventos do nosso *Observable* sem precisar chamar manualmente o ``unsubscribe()``.

### E as chamadas http pelo ``HttpClient``, também devo me preocupar ?

Essas estão na conta do Angular. Podemos confirmar [no código fonte](https://github.com/angular/angular/blob/master/packages/common/http/src/xhr.ts), a partir da **linha 208**, que após a resposta do servidor é chamado o ``complete()`` do *Observable* que pelo seu [contrato](http://reactivex.io/documentation/contract.html), indica que já não vai mais emitir evento nenhum.
Então, menos uma preocupação pra gente :)

### Resumindo

Essas são algumas maneiras de anular a inscrição de um *Observable*, ajudando a manter a aplicação saudável, sem uso desnecessário dos recursos do seu cliente e evitando problemas a medida que a aplicação cresce.

Não deixe seus *Observables* espalhados por aí ou eles podem te assombrar depois.


![Isso é tudo pessoal](https://upload.wikimedia.org/wikipedia/commons/e/ea/Thats_all_folks.svg)