26 Jul · 4 min read
RxJS is a very powerful library for reactive programming providing benefits such as high-quality API, flexibility, and in general, it makes programming easier. But RxJS is not a silver bullet, and it comes with a few challenges which if not handled properly will cause poor user experience or even the application to crash. One of these challenges is Memory Leak.
Before jumping straight into how to avoid memory leaks in Angular, let's first discuss what a memory leak is. A memory leak is a type of resource leak caused by poor management of memory allocation in a way that is not cleared from the memory when is not needed anymore. The memory leak is one of the worst problems developers could have since it is very difficult to debug and catch. What is even worse, is that Memory Leak is not a bug that functional tests would catch, which highly increases the chance for the application to be deployed to production without any sign of a problem. Respectively, this would lead to slow performance or even a crashing application.
In the context of Angular, most often memory leaks are caused due to bad management of Observables. For example, have you ever seen code like the example below? Well, this is arguably the most common way of causing memory leaks in Angular and I admit that I made this mistake in my early days when I started using Angular.
constructor(private stockService: StockService) { }
ngOnInit(): void {
this.stockService.getStocks().subscribe((stocks: Stock[]) => {
this.stocks = stocks;
})
}
So, let’s see two ways how to prevent this major pitfall when using Angular.
The recommended approach for handling Observables in Angular is by using the Angular async pipe. The async pipe subscribes to an Observable and returns the latest value it has emitted. The async pipe marks the component to be checked for changes every time a new value is emitted. When the component is destroyed there is no need to unsubscribe because the async pipe automatically unsubscribes preventing memory leaks. In addition, the async pipe makes the code more readable.
This is an example of the usage of the async pipe.
@Component({
selector: 'app-stock',
template: `
<div *ngIf="stocks$ | async as stocks">
<ul>
<li *ngFor="let stock of stocks">
{{ stock.symbol }}
</li>
</ul>
</div>
`,
styleUrls: ['./stock.component.scss'],
})
export class StockComponent implements OnInit {
stocks$: Observable<Stock[]> = this.stockService.getStocks();
constructor(private stockService: StockService) {}
ngOnInit(): void {}
}
In cases when the async pipe is not a good solution then a manual subscription is required. In addition to preventing memory leaks, it is mandatory to unsubscribe properly from Observable. For example, the RxJS take until operator in combination with destroy$ Subject can help in this case. Let’s see how.
export class StockComponent implements OnInit, OnDestroy {
private destroy$ = new Subject<void>();
stocks: Stock[] = [];
constructor(private stockService: StockService) {}
ngOnInit(): void {
this.stockService
.getStocks()
.pipe(takeUntil(this.destroy$))
.subscribe((stocks: Stock[]) => {
this.stocks = stocks;
});
}
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
}
}
We can see from the example above, that we implement the ngOnDestroy lifecycle hook. In addition, we know from RxJS documentation that takeUntil operator completes the stream when its provided Observable emits a value. In our case, we provide the destroy$ stream as input to the takeUntil operator. When the component is destroyed, destroy$ emits a value that will clean the subscription and thus prevent a memory leak.
In addition, the example below shows if we have multiple subscriptions we just need to add to each one takeUntil(this.destroy$) which guarantees that each subscription will be cleaned when the component gets destroyed.
ngOnInit(): void {
this.stockService
.getStocks()
.pipe(takeUntil(this.destroy$))
.subscribe((stocks: Stock[]) => {
this.stocks = stocks;
});
this.stockService
.getNewestStock()
.pipe(takeUntil(this.destroy$))
.subscribe((newestStock: Stock) => {
this.newestStock = newestStock;
});
}
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
}
Today, performance is a crucial asset for each application in terms of user experience. Consequently, preventing memory leaks is vital. Unfortunately, the RxJS library can be easily misused when working with Subscriptions which can cause memory leaks. We demonstrated two ways of correctly working with RxJS that prevent memory leaks – by using Async Pipe or On Destroy in combination with takeUntil.
Comment as
Login or comment as
0 comments