- vừa được xem lúc

RXJS với Angular, subscribe và unsubscribe Observable

0 0 49

Người đăng: Doãn Trọng Tài

Theo Viblo Asia

Khi làm việc với Angular, chắc hẳn các bạn đã nghe và thường xuyên nhắc đến RXJS, Observable. Nó là một phần không thể thiếu để làm việc với các services, providers của Angular. Tuy nhiên, ngoài việc tạo ra các phương thức tiện lợi để xử lý các luồng dữ liệu và sự kiện thì đôi khi nó cũng mang đến cho chúng ta đôi chút rắc rối. Trong bài viết này, mình sẽ nêu lên một vài điểm cần chú ý từ chính các phương thức được sử dụng nhiều nhất với Observable là subscribeunsubscribe. Về khái niệm RXJS là gì, Observable là gì thì các bạn có thể tìm đọc các tài liệu khác, mình sẽ không nhắc lại trong bài viết này.

subscribe và unsubscribe

Khi chúng ta làm việc với Observable thì sẽ thường xuyên sử dụng phương thức subscribe để theo dõi các thay đổi của dữ liệu hoặc nhận về kết quả của một công việc nào đó.

VD việc sử dụng với HttpClient service của Angular:

import { HttpClient } from '@angular/common/http';
// ...
// inject http
constructor(private http: HttpClient) { }
// ...
// use http
const apiUrl = 'http://abc.com/def';
this.http.get(apiUrl).subscribe(val => console.log(val));

Khi API trả về kết quả thì dữ liệu đó sẽ được log ra qua phương thức subscribe. Và để kết thúc quá trình lắng nghe kết quả từ API trả về thì chúng ta sẽ xử dụng đến phương thức unsubscribe.

// ...
this.http.get(apiUrl).subscribe(val => console.log(val)).unsubscribe();

Nếu unsubscribe không được triệu gọi thì luồng dữ liệu đó sẽ được lưu vào bộ nhớ và theo dõi, bất cứ khi nào có sự thay đổi của dữ liệu trong luồng thì phương thức subscribe sẽ được thực thi. Tuy nhiên, trong thực tế, 1 request lấy dữ liệu từ API thì cũng chỉ nhận được 1 response duy nhất, cho nên chúng ta không thể thấy việc phương thức subscribe được khởi chạy lần tiếp theo, và service HttpClient cũng tự gọi một phương thức để kết thúc quá trình lắng nghe kết quả từ API. Bởi vậy, chúng ta thường không cần dùng đến phương thức unsubscribe trong các trường hợp này.

Vấn đề với unsubscribe

Ở ví dụ trên thì chúng ta có thể lãng quên hoặc thấy thằng unsubscribe chẳng có nhiều tác dụng, nhưng nếu trong các trường hợp khác mà các bạn cũng bỏ qua việc unsubscribe một Observable thì hậu quả của nó thật khôn lường. Ngoài việc rò rỉ bộ nhớ do phải lưu các Observable để subscribe gây sút giảm hiệu năng của ứng dụng, nó còn mang đến các kết quả dở khóc dở cười cho các New Dev.

  • VD: Giả xử các bạn có một component A làm nhiệm vụ request lên API để lấy data khi button reload của một component B được click. Component A sẽ kiểm tra khi nào button reloadcomponent B được click thông qua một service C. Component A có thể ẩn, hiện tùy vào thao tác của người dùng.
// app.component.ts
import { Component } from '@angular/core'; @Component({ selector: 'my-app', template: `<button (click)="showA = !showA">Toggle</button> <p>{{showA ? 'component A is display' : 'component A is hidden'}}</p> <b-component></b-component> <a-component *ngIf="showA"></a-component>`
})
export class AppComponent { showA = true;
}
// c.service.ts
import { Injectable } from '@angular/core';
import { Subject } from 'rxjs'; @Injectable()
export class CService { buttonSubject: Subject<any> = new Subject(); buttonObservable = this.buttonSubject.asObservable();
}
// a.component.ts
import { Component, OnInit } from '@angular/core';
import { CService } from './c.service'; @Component({ selector: 'a-component', template: `<h1>GET and show data!</h1>`
})
export class AComponent implements OnInit { constructor(private cService: CService) { } ngOnInit() { this.cService.buttonObservable.subscribe(e => { console.log('GET api'); }); }
}
// b.component.ts
import { Component } from '@angular/core';
import { CService } from './c.service'; @Component({ selector: 'b-component', template: '<button (click)="handleButtonClick($event)">Reload</button>'
})
export class BComponent { constructor(private cService: CService) { } handleButtonClick(e) { this.cService.buttonSubject.next(e); }
}

Ở VD này, khi các bạn click vào button reload sẽ thấy tại cửa sổ console log ra thông báo GET api, tức là component A đã lắng nghe được sự kiện click của button reload trong component B và thực hiện công việc GET api.

Tuy nhiên, khi component A bị ẩn đi thì các bạn cũng không mong muốn component A tiếp tục làm công việc GET api, nhưng hãy thử click lại button reload xem nhé:

Các bạn thấy component A đã được ẩn đi, nhưng nó vẫn tiếp tục thực hiện công việc GET api, điều đó là do chúng ta chưa thực hiện unsubscribe buttonObservable từ service C.

// a.component.ts
// ...
ngOnInit() { this.cService.buttonObservable.subscribe(e => { console.log('GET api'); }); // No unsubscribe
}
// ...

Và khi component A được hiển thị trở lại thì nó sẽ tiếp tục thực hiện một luồng công việc mới. Do đó, công việc GET api sẽ bị lặp lại nhiều lần, điều này rất có hại cho hiệu năng của ứng dụng và cả API.

Nếu component A được ẩn hiện N lần, thì công việc GET api cũng sẽ bị lặp lại N lần. Để khắc phục tình trạng này, các bạn chỉ cần unsubscribe buttonObservable là được.

// a.component.ts
// ...
ngOnInit() { const subscription = this.cService.buttonObservable.subscribe(e => { console.log('GET api'); // unsubscribe subscription.unsubscribe(); });
}
// ...

Nhưng lúc này, việc GET api sẽ chỉ được thực hiện một lần duy nhất cho dù các bạn có click vào button reload bao nhiêu lần đi nữa, bởi vì sau khi công việc của lần click đầu tiên được thực hiện thì quá trình lắng nghe sự kiện click cũng bị dừng lại do phương thức unsubscribe.

Vậy nên, để mọi việc diễn ra xuôn sẻ, các bạn hãy unsubscribe khi mà component được gỡ khỏi DOM thông qua lifecycle hook ngOnDestroy của Angular.

// a.component.ts
import { Component, OnInit, OnDestroy } from '@angular/core';
import { SubscriptionLike } from 'rxjs';
import { CService } from './c.service'; @Component({ selector: 'a-component', template: `<h1>GET and show data!</h1>`
})
export class AComponent implements OnInit, OnDestroy { subscription: SubscriptionLike; constructor(private cService: CService) { } ngOnInit() { this.subscription = this.cService.buttonObservable.subscribe(e => { console.log('GET api'); }); } ngOnDestroy() { // unsubscribe if (this.subscription) { this.subscription.unsubscribe(); } }
}

Một số cách để unsubscribe hiệu quả

Qua VD trên thì các bạn cũng đã hiểu được sự quan trọng của việc kiểm xoát và unsubscribe các Observable một cách hợp lý. Nhưng giả xử trong một component có quá nhiều các Observable cần được unsubscribe thì các bạn sẽ làm thế nào để hiệu quả nhất?

ngOnInit() { this.subscription1 = request1.subscribe(...); this.subscription2 = request2.subscribe(...); // ... this.subscriptionX = requestX.subscribe(...);
} ngOnDestroy() { if (this.subscription1) { this.subscription1.unsubscribe(); } if (this.subscription2) { this.subscription2.unsubscribe(); } // ... if (this.subscriptionX) { this.subscriptionX.unsubscribe(); }
}

Nếu làm như đoạn code trên thì thật là kinh khủng đúng không? Vậy hãy cùng thử một số cách dưới đây nhé!

Sử dụng một mảng để quản lý các subscriptions

subscriptions: Subscription[] = [];
ngOnInit() { this.subscriptions.push(request.subscribe(...)); this.subscriptions.push(request.subscribe(...)); this.subscriptions.push(request.subscribe(...));
}
ngOnDestroy() { this.subscriptions.forEach((subscription) => subscription.unsubscribe());
}

Sử dụng Subscription add

subscriptions: Subscription = new Subscription();
ngOnInit() { this.subscriptions.add(request.subscribe(...)); this.subscriptions.add(request.subscribe(...)); this.subscriptions.add(request.subscribe(...));
}
ngOnDestroy() { this.subscriptions.unsubscribe();
}

Sử dụng takeUntil operators

import { takeUntil } from 'rxjs/operators';
// ...
destroy: ReplaySubject<any> = new ReplaySubject<any>(1);
ngOnInit() { request.pipe(takeUntil(this.destroy)).subscribe(); request.pipe(takeUntil(this.destroy)).subscribe(); request.pipe(takeUntil(this.destroy)).subscribe();
}
ngOnDestroy() { this.destroy.next(null);
}

Lời kết

Qua bài viết này, mình hi vọng sẽ truyền đạt được cho các bạn một chút kiến thức nho nhỏ và cũng là kinh nghiệm của mình khi làm việc với RXJS và Angular. Nếu các bạn có gặp một số trường hợp tương tự như bài viết này thì hãy áp dụng các cách mà mình đã nói ở trên nhé! Ngoài các cách để unsubscribe một Observable như bên trên thì cũng còn một vài cách nữa, nhưng nó cũng có phần phức tạp và không tiện dụng cho lắm nên mình cũng không nêu ra, cảm ơn tất cả mọi người đã quan tâm theo dõi!

Code demo: Các bạn có thể xem VD tại đây

Bình luận

Bài viết tương tự

- vừa được xem lúc

Giới thiệu Typescript - Sự khác nhau giữa Typescript và Javascript

Typescript là gì. TypeScript là một ngôn ngữ giúp cung cấp quy mô lớn hơn so với JavaScript.

0 0 500

- vừa được xem lúc

Cài đặt WSL / WSL2 trên Windows 10 để code như trên Ubuntu

Sau vài ba năm mình chuyển qua code trên Ubuntu thì thật không thể phủ nhận rằng mình đã yêu em nó. Cá nhân mình sử dụng Ubuntu để code web thì thật là tuyệt vời.

0 0 374

- vừa được xem lúc

Đặt tên commit message sao cho "tình nghĩa anh em chắc chắn bền lâu"????

. Lời mở đầu. .

1 1 701

- vừa được xem lúc

Tìm hiểu về Resource Controller trong Laravel

Giới thiệu. Trong laravel, việc sử dụng các route post, get, group để gọi đến 1 action của Controller đã là quá quen đối với các bạn sử dụng framework này.

0 0 335

- vừa được xem lúc

Phân quyền đơn giản với package Laravel permission

Như các bạn đã biết, phân quyền trong một ứng dụng là một phần không thể thiếu trong việc phát triển phần mềm, dù đó là ứng dụng web hay là mobile. Vậy nên, hôm nay mình sẽ giới thiệu một package có thể giúp các bạn phân quyền nhanh và đơn giản trong một website được viết bằng PHP với framework là L

0 0 421

- vừa được xem lúc

Bạn đã biết các tips này khi làm việc với chuỗi trong JavaScript chưa ?

Hi xin chào các bạn, tiếp tục chuỗi chủ đề về cái thằng JavaScript này, hôm nay mình sẽ giới thiệu cho các bạn một số thủ thuật hay ho khi làm việc với chuỗi trong JavaScript có thể bạn đã hoặc chưa từng dùng. Cụ thể như nào thì hãy cùng mình tìm hiểu trong bài viết này nhé (go).

0 0 414