Tratando vazamento de memória em aplicações Angular.

Quando começamos o aprendizado de uma aplicação Angular, aprendemos o básico de RxJS para criar nossos componentes, principalmente quando temos que realizar alguma requisição HTTP. ( link )

O conceito de Observable implementado no RxJS é poderoso e nos permite acessar um fluxo de dados (como uma requisição Http) de uma maneira fluída e organizada.

Nesse exemplo eu estou fazendo uma requisição na API e me inscrevendo (subscribe) no retorno para atribuir a um objeto que usarei na minha interface.

Porém, nesse simples exemplo mora um perigo não muito claro, a ameaça silenciosa do vazamento de memória.

Quando esse componente não for mais utilizado, como por exemplo se o usuário ir para outra página, o Observable gerado por essa requisição HTTP ainda estará ativo pois o componente não se desinscreveu nele.

Como o componente já foi destruído, esse Observable ainda ativo virou um vazamento de memória(memory leak).

Isso causa uma degradação do desempenho, aumento do consumo de recursos da máquina e principalmente, perda de tempo do desenvolvedor, pois em uma aplicação grande esse tipo de erro é bem complicado de se localizar.

Então, o que podemos fazer ? Simples: garantir que meu componente se desinscreva sempre que ele não precisar mais do Observable!

OK, mas como fazer isso? Ah! Para isso temos algumas maneiras…

A Básica.

Para se desinscrever de um Observable basta você seguir dois passos:

  • Guardar a inscrição: O método subscribe tem como retorno um objeto do tipo subscription que representa a inscrição realizada.

  • Chamar o método unsubscribe do objeto subscription.

Isso é simples, porém a questão é quando chamar o unsubscribe.

No Angular os componentes possuem métodos de ciclo de vida ( Lifecycle Hooks ), normalmente quando criamos via cli (ng g c) ele já vem de fábrica com o método OnInit.

Para desinscrever o componente do Observable antes dele ser destruído, podemos então utilizar o método OnDestroy.

Pronto vazamento eliminado!

Porém nem sempre nossos componentes terão apenas um Observable,assim essa solução simples pode ficar um pouco feia …

Veja que adicionamos mais um Observable e tivemos que guardar mais uma Subscription e realizar mais uma condição no método onDestroy.

Podemos melhorar?

O SubSink

Entra a biblioteca SubSink focada em facilitar a coleta e a desinscrição nos Observables.

Ela é muito simples e tem apenas um método e um atributo:

  • O atributo sink onde você adiciona a inscrição em uma lista que fica implícita.

  • O método unsubscribe que realiza a desinscrição de toda a lista do atributo sink.

Vamos ao exemplo anterior verificar como fica mais simples a administração dos Observables do componente.

Ainda temos mais uma alternativa. E essa já vem por padrão no Angular.

O pipe async.

Quando componentes utilizam Observables em seus templates HTML uma alternativa muito interessante é o pipe async ( | async).

Com ele o componente apenas passa para o template o observable. E o motor de renderização do Angular faz o trabalho de realizar a inscrição (subscribe) e atribuir o retorno a um objeto.

A melhor parte é que o Angular cuida da desinscrição para você!

Vamos ver como ficam o template e o componente:

Veja que não temos que chamar o OnDestroy, o framework faz isso para você!

Para mais informações, segue o vídeo da Loiane sobre o tema( vídeo ).

Conclusão

Nesse artigo vimos os cuidados que temos que ter para evitar vazamento de memória (memory leaks) nas nossa aplicações Angular.

Resumindo:

  • Se seu componente utiliza observables para apresentar informações no template, utilize o pipe async.

  • Se você tem apenas um observable onde você precise fazer a inscrição(subscribe), guarde o objeto Subscription em uma variável e se desinscreva no método OnDestroy do componente.

  • Caso seu componente tenha vários observables, utilize a bibiloteca SubSink para seu código ficar mais limpo.

Código fonte do exemplo( link ).

Obrigado e compartilhe esse artigo com seus colegas para eles conhecerem esses cuidados na construção de componentes Angular.

Comments (3)

Add a comment
Fábio Miranda's photo

Se o observable for usado para pegar a informação uma única vez, dá pra usar também o operador take do rxjs com 1 de parâmetro.

Só é importante saber que ele vai ser destruído(subscrito) apenas quando emitir o valor, pra poder organizar os cenários de uso.

Só deixando um exemplo:

    this.subPeople = this.http
      .get<People>('https://swapi.co/api/people/1/')
      .pipe(take(1))
      .subscribe((data: People) => {
        this.people = data;
      });
Show all replies
Fábio Miranda's photo

Software Developer

Isso é verdade. Aparentemente a galera começa a desenvolver sem entender um pouco a parte mais teórica da tecnologia e aí não sabe que isso pode causar grandes problemas. Eu já peguei um projeto onde estava tendo vários estouros de memória, e o problema era justamente esse de esquecer de matar os observables. Era até engraçado, porque a maioria das chamadas poderia ter sido feita apenas com o Pipe Async e resolveria o problema.

Mas enfim, excelente artigo. Eu estou escrevendo um sobre dicas básicas de performance no Angular, e provavelmente citarei aqui o seu lá =)