Angular — Observable Nesnelerden Nasıl Unsubscribe Olunur?

Nahit Ferhat Ektaş
4 min readOct 22, 2023

--

Geliştirdiğimiz tüm Angular uygulamalarında Rxjs’ i yoğun bir şekilde kullanıyoruz. Örneğin, HttpClient kütüphanesi ile uzak api’lere attığımız istekler bize observable dönmesini tercih ediyoruz. Dönen observable(gözlemlenebilir) nesnelere, observer(gözlemci) ile subscribe(abone) olarak akıştaki değerleri alıyoruz.

Ancak bu subscription sürecini ihtiyacımız karşılandıktan sonra sonlandırmazsak memory leaks hataları ile karşılaşabiliriz.

Observable nesneler, kendilerine abone olan observer’ lara işlem complete olana kadar veri tedarik eder. Ama bazı senaryolarda işlem complete olmaz ve observer’larımız observable nesneleri takip etmeyi sürdürür.

Örneğin, veri eklemek için bir pop-up oluşturduk ve pop-up açıldığında input’larımıza verileri girdikten sonra “Save” button’una basıp kayıt ekleme api’sine istek atarak kayıt işlemi gerçekleştiriyoruz. “Save” button’una bastıktan sonra api’den gelen response süresinin uzadığını ve bunun sonucunda kullanıcı sayfanın çalışmadığını düşünerek pop-up’ı kapattığını düşünelim. Observable nesnesi hala api’nin cevabını bekliyor yani complete olmadı, dolayısı ile hala observable nesnemize aboneyiz ve bellekte dinlemeye devam ediyoruz, ancak işlemi yapacağımız pop-up component’ini destroy ettik. Bu veya buna benzer işlemlerin sayısı arttıkça, bellek kullanımı artıyor ve memory leaks hataları almamıza neden olabiliyor.

Peki bu memory leaks hatalarından kaçınmak için neler yapabiliriz?

1- Async | Pipe Kullanımı

Observable nesnelerimize async | pipe ile abone olduğumuzda bize tüketilen son değeri verir. Component ilk yüklendiğinde async | pipe ile otomatik olarak observable nesnelerimize abone olur, component destroy olduğunda da otomatik olarak aboneliği sonlandırmış oluruz. Observable nesnemiz yeni bir değer ürettiğinde, async | pipe ile bu değişikliği algılar ve sayfayı yeniden render eder.

import { Component } from '@angular/core';
import { ProductService } from './product.service';

@Component({
selector: 'app-root',
template: `<ng-container *ngIf="productList$ | async as productList">
<ul>
<li *ngFor="let product of productList">
{{ product.name }}
</li>
</ul>
</ng-container>`,
styleUrls: ['./app.component.css'],
})
export class AppComponent {
productList$ = this.productService.getProducts();

constructor(private productService: ProductService) {}
}

Yukarıdaki örnekte, önce ProductService sınıfımızı component’e inject ettik ve yine ProductService de yer alan, geriye observable nesnesi dönen getProducts() fonksiyonunu productList$ degişkenine atama işlemi yaptık. Daha sonrasında da html sayfamızda productList$ | async as productList ile observable nesnesine component ilk yüklendiği sırada otomatik olarak abone olduk. Component destroy oluduğunda unsubscription işlemi otomatik olarak gerçekleşecektir.

2- Unsubscription

Bu yöntem ile component’imizin OnDestroy lifecycle’ına ilgili observable nesnesinin unsubscribe olacağını belirtmemiz gerekiyor.

export class AppComponent implements OnInit, OnDestroy {
subscription!: Subscription;

constructor(private productService: ProductService) {}

ngOnInit(): void {
this.subscription = this.productService
.getProducts()
.subscribe((x) => console.log(x));
}

ngOnDestroy(): void {
this.subscription.unsubscribe();
}
}

Yukarıda önce Subscription tipinde bir değişken tanımladık.

subscription!: Subscription

Daha sonrasında component’imiz ilk yüklendiğinde subscribe(abone) olduğumuz product service sınıfı içerisindeki getProduct fonksiyonunu tanımladığımız subscription nesnesine atadık. Component’imiz destroy olduğunda, subscription nesnemizinin unsubscribe fonksiyonunu çağırarak aboneliğimizi sonlandırdık.

Birden fazla subscription nesnemiz var ise, aşağıdaki gibi gruplayarak tüm subscription’larımızı unsubscribe edebiliriz.

export class AppComponent implements OnInit, OnDestroy {
subscription!: Subscription;

constructor(private productService: ProductService) {}

ngOnInit(): void {
const getProducts$ = this.productService
.getProducts()
.subscribe((x) => console.log(x));

const getActiveProducts$ = this.productService
.getActiveProduct()
.subscribe((x) => console.log(x));

this.subscription.add(getProducts$);
this.subscription.add(getActiveProducts$);
}

ngOnDestroy(): void {
this.subscription.unsubscribe();
}
}

Abone olduğumuz observable nesnelerini

this.subscription.add(getProducts$);
this.subscription.add(getActiveProducts$);

tek bir subscription içerisinde grupladık ve component destroy olduğunda unsubscribe işlemini gerçekleştirdik.

3- Rxjs TakeUntil Operatörü

TakeUntil operatörü, observable nesnelerimizi iptal etmenin bir başka yoludur. Observable nesnelerimize abone olmadan hemen önce pipe yardımı ile başka bir subject ekleyebilir ve eklediğimiz subject’i tetiklememiz durumunda observable nesnemiz iptal olmuş olur.

export class AppComponent implements OnInit, OnDestroy {
destroy$ = new Subject<boolean>();

constructor(private productService: ProductService) {}

ngOnInit(): void {
const getProducts$ = this.productService
.getProducts()
.pipe(takeUntil(this.destroy$))
.subscribe((x) => console.log(x));
}

ngOnDestroy(): void {
this.destroy$.next(true);
this.destroy$.complete();
}
}

Yukarıdaki örnekte, destroy$ değişkenine boolean değer alan yeni bir subject tanımladık. getProduct() fonksiyonu çalışırken, pipe(takeUntil(this.destroy$)) ile akışın arasına girerek daha önce oluşturduğumuz değişkeni takeUntil operatörünün içine ekledik. Artık component destroy olduğu zaman, oluşturduğumuz destroy$ subject’ini tetiklemiş olacağız ve abonelik işlemi sona erecektir.

TakeUntil operatörünü kullanırken dikkat edilmesi gereken kritik nokta, takeUntil her zaman subscription dan önce olmalıdır. Eğer takeUntil ile subscription arasına başka bir operatör akışa eklenirse takeUntil çalışmayacaktır.

export class AppComponent implements OnInit, OnDestroy {
destroy$ = new Subject<boolean>();
search = new UntypedFormControl();

constructor(private productService: ProductService) {}

ngOnInit(): void {
this.search.valueChanges
.pipe(
takeUntil(this.destroy$),
switchMap((data: string) => {
console.log(data);
return this.productService.getProducts(data);
})
)
.subscribe((res) => console.log(res));
}

ngOnDestroy(): void {
this.destroy$.next(true);
this.destroy$.complete();
}
}

Yukarıdaki örnekte search adında bir form nesnesi tanımlıyoruz. Search nesnesinin value değeri her değiştiğinde, switchMap operatörü ile akışı değiştiriyor ve yeni gelen değere göre yeni bir akış başlatıyoruz. Eğer takeUntil operatörünü yukarıdaki gibi subscription öncesinde yer alan ve subscription sürecine etki eden bir operatörden önce kullanırsak muhtemelen unsubscription işlemi gerçekleşmeyecektir.

export class AppComponent implements OnInit, OnDestroy {
destroy$ = new Subject<boolean>();
search = new UntypedFormControl();

constructor(private productService: ProductService) {}

ngOnInit(): void {
this.search.valueChanges
.pipe(
switchMap((data: string) => {
console.log(data);
return this.productService.getProducts(data);
}),
takeUntil(this.destroy$),
)
.subscribe((res) => console.log(res));
}

ngOnDestroy(): void {
this.destroy$.next(true);
this.destroy$.complete();
}
}

Yukarıda da gösterildiği gibi, takeUntil’in yeri switchMap sonrasında olmalıdır.

4- TakeUntilDestroy

Angular 16 ile birlikte gelen bu yenilik sayesinde observable nesnelerimizi yine takeUntil’e benzer şekilde unsubscribe edebiliyoruz. Tek farklı tarafı TakeUntilDestroy ile ngOnDestroy lifecycle hook’u çağırmamıza gerek kalmıyor. Angular bunu otomatik olarak algılıyor.

constructor() {
this.productService
.getProducts()
.pipe(takeUntilDestroyed())
.subscribe((x) => console.log(x));
}

Yukarıdaki örnekte olduğu gibi takeUntilDestroyed() ile component’imiz destroy olduğunda unsubscription işlemi gerçekleşecektir.

Observable nesnelerimize subscribe(abone) olmamız bize bellek tarafında maliyet oluşturacaktır, bu da uygulamanın performansını etkilediği gibi belli bir noktadan sonra memory leaks hatasına dönüşecektir. Bunun önüne geçmek için, ihtiyaç duymadığımız abonelikleri sonlandırmamız gerekiyor.

Umarım faydalı olmuştur. Anlaşılmayan veya akılda soru işareti olan noktalar için nahitektas@gmail.com adresinden ulaşabilirsiniz.

--

--