[Angular] RxJs unsubscribe 的時機
本文主要參考下列文章以及內附連結,整理一下在 Angular 運用 RxJs 的注意點及 不需 unsubscribe
的時機:
- Angular/RxJs When should I unsubscribe from ‘Subscription’
- Router - auto unsubscribe from the params observable is not working
- RxJS: Don’t Unsubscribe
Observable 的訂閱(Subscription)需要在之後 unsubscribe()
主要是避免 memory leak、或重複訂閱造成預期外的行為 aka 八阿哥,但通常是對無限或永遠不會結束(complete)的 Observable。換言之,有限或自己會結束的 Observable 是不用特別去呼叫 unsubscribe()
的。
有限的 Observable
httpClient
1 | this.http.post(...).subscribe(...) |
雖然正常情況下 http 請求無論成功錯誤都是會完成,例外是當網路或 server 回應過慢時,使用者可能在 XHR 未完成前就先關閉了 component,subscriber 仍會在完成時被呼叫,如有已參照的變數被 destroyed 將會有不預期的副作用,看需求決定要不掉 unsubscribe。
ActivatedRoute
1 | this.route.params(...).subscribe(...) |
參閱文件,Router 會自動處理無作用的 ActivatedRoute,如果想寫 unsubscribe 當作練習也沒差。
AsyncPipe
1 | <input type="text" [value]="text$ | async"> |
1 | this.text$ = this.textService.getText(); |
Async pipe 的訂閱會跟著 component 的關閉一併 destory。
善用pipe
RxJS 提供了 take()
、takeWhile()
、takeUntil()
、first()
等 pipe,幫助我們將 Observable 轉換成有限的,也就不需手動 unsubscribe。
寫在 Service 裡的 Observable
內部的訂閱
Angular 的 service 通常是 singleton(單例),生命週期會一直存活直到整個 web app 分頁被關閉,同時也終止了 JS runtime 與釋放記憶體,因此僅在 service 內部的訂閱,不會有重複訂閱與 memory leak 的問題,不需要 unsubsribe。
共用 Observable
若 service 是共享的,提供 Observable 給不同的 component 使用與訂閱,同一個 component 可能會重複 init 與 destroy ,就必須要對訂閱做好 unsubscribe 管理。
更好的 unsubscribe 方案
除了以上列出例外,幾乎所有的 Observable 都需要手動 unsubscribe 了,一個簡單的 component 通常不會有太多訂閱,但當 code 因需求越長越肥時,不保證會不會寫到厭世,下列方案還沒親自運用過,寫起來以備不時之需:
安裝第三方套件 @w11k/ngx-componentdestroyed 管理,參考這篇文章
自訂一個 Subject 並讓所有 Observable 都用
takeUntil()
接上它統一管理:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22private ngUnsubscribe = new Subject();
constructor(private booksService: BookService) { }
ngOnInit() {
this.booksService.getBooks()
.pipe(
startWith([]),
filter(books => books.length > 0),
takeUntil(this.ngUnsubscribe)
)
.subscribe(books => console.log(books));
this.booksService.getArchivedBooks()
.pipe(takeUntil(this.ngUnsubscribe))
.subscribe(archivedBooks => console.log(archivedBooks));
}
ngOnDestroy() {
this.ngUnsubscribe.next();
this.ngUnsubscribe.complete();
}參考這篇文章,自訂一個
*ngSubscribe
的 directive,就能像 AsyncPipe 一樣寫後不理:1
2
3
4
5
6<section *ngSubscribe="alerts$ as alerts">
<app-alerts [alerts]="alerts"></app-alerts>
<div>
<other-component [alerts]="alerts"></other-component>
</div>
</section>