Vazamento de memória com RxJS Observables
Angular usa RxJS como um pilar, que por sua vêz utiliza o conceito de Observable e Observers, onde Observable é oque pode ser observado, ou a fonte de dados, enquanto que Observer é que observa, ou recebe os dados. Lidar com Observables pode ser perigoso porque existe a possibilidade de criar um vazamento de memória. Como isso é possível?
Problema
Sempre que um componente ou diretiva é destruido, todos os “subscriptions” de Observables permanecem ativos.
Solução
Manualmente desinscrever de todos os Observables customizados quando um componente ou diretiva é destruído. O melhor lugar para fazer o “unsubscribe” é na função utilizando o ciclo de vida OnDestroy. Alguns não precisam deste cuidado, como o router e o http, para o resto há outras soluções:
- executar “unsubscribe” após o subscription;
- usar o operador takeUntil
- usar async pipe
Unsubscribe
export class UnsubscribeCardComponent implements OnInit, OnDestroy {
message: string;
subscription: Subscription;
constructor(private upperCaseService: UpperCaseService) {}
ngOnInit() {
this.subscription = this.upperCaseService.getUpperCaseMessage()
.subscribe((message: string) => this.message = message);
}
ngOnDestroy(): void {this.subscription.unsubscribe();}
}
TakeUntil
export class TakeUntilCardComponent implements OnInit, OnDestroy {
message: string;
private unsubscribe$ = new Subject();
constructor(private upperCaseService: UpperCaseService) {}
ngOnInit() {
this.upperCaseService.getUpperCaseMessage()
.takeUntil(this.unsubscribe$)
.subscribe((message: string) => this.message = message);
}
ngOnDestroy(): void {
this.unsubscribe$.next();
this.unsubscribe$.complete();
}
}
TakeUntil leva um segundo Observable como um argumento, ele monitora a segunda assinatura Observable e descarta depois que ele emite um valor ou termina.
takeWhile
Outra opção para realizar o unsubscribe é o utilizar o takeWhile
export class MyComponent implements OnDestroy, OnInit {
public user: User;
private alive: boolean = true;
public ngOnInit() {
this.userService.authenticate(email, password)
.takeWhile(() => this.alive)
.subscribe(user => {
this.user = user;
});
}
public ngOnDestroy() {
this.alive = false;
}
}
Subscribe.add
AsyncPipe
export class AsyncPipeCardComponent implements OnInit {
messageSubscription: Observable<string>;
constructor(private upperCaseService: UpperCaseService) {}
ngOnInit() {
this.messageSubscription = this.upperCaseService.getUpperCaseMessage();
}
}<h4 class="card-title">{{messageSubscription | async}}</h4>
Quando o componente é destruido, o async pipe é desinscrito (unsubscribes) automáticamente. Mas cuidado, apesar de conveniente, o async pipe pode levar sua aplicação a uma lentidão.
Conclusão
Quando componentes e diretivas são destruídos todos os Observables precisam ser “desinscritos” (unsubscribed) manualmente. Async pipe é uma boa solução, porque faz tudo sozinho. O desenvolvedor líder da RxJS Ben Lesh recomenda usar o takeUntil, ele explica neste artigo.
Código de exemplo aqui.
Demo aqui.
Mais sobre RxJS
Texto original:
Recomendação de leitura
Build Reactive WebSites with RxJS
Reactive Programming with RxJS 5: Untangle Your Asynchronous JavaScript Code