Skip to main content

[Angular] RxJS - 10 common operators

RxJS 是一個在 angular 框架的專案中被廣泛使用的工具,原因是在每個 angular 專案本身就已經下載了這個套件,在 http 請求的時候都會回傳 RxJS 中的 Observable,Observable 我們可以將他理解成一個資料儲存站,我們要使用這個資料儲存站的資料的話,要去訂閱 (subscribe) 它,然後如果要對資料做些轉換的話,可以透過 RxJS 裡面超過 100 種的 operators 去做轉換,再去做資料的呈現、儲存等等。這篇文章主要是從 Joshua Morony youtube 影片 來作筆記。

map and filter​

map 和 filter 跟 JavaScript 中的陣列操作方法很像,當我們在 Observable 回傳資料時,我們會想對每個資料做些變化、篩選的時候就可以使用,值得注意的是會出現類似下面的城市寫法,

map((x) => x.map((n) => n * 10));

前面的 map 是針對 observable 傳出的每筆資料進行操作,而當回傳資料又是一個陣列,我們會再透過 js 的 map 去對陣列中的每個值進行操作。

tap​

Tap operator 主要是用來達成一些 side effects,像是通知、儲存資料、console.log、轉導路徑等等,加入它或是移除它都對原本的資料流是沒有影響的。

tap((x) => console.log(x));

switchMap, concatMap​

switchMap, concatMap, mergeMap 這幾個 operators 常用來做兩個以上的資料流操作,像是我想要將第一個資料留的資料帶入第二個資料流,這時候可以透過 switchMap 來達成,而 switchMap 和 concatMap 之間的差別在於,當我在第一個資料流還沒處理完的時候,第一個資料流又有新的資料,此時,switchMap 會中斷前一個資料流的操作,改為取新的資料流重新進行操作;而 concatMap 則是會等到舊的資料跑完,再去執行新的資料流,兩者的細微差異值得注意。常見範例如下,會取得網址的參數,再進行 API 呼叫等操作。

feedback$ = this.route.paramMap.pipe(
switchMap((params) =>
this.clientStore.feedback$.pipe(
map((feedbacks) =>
feedbacks
? feedbacks.find((feedback) => feedback.id === params.get("id"))
: null
)
)
)
);

mergeMap 則是可以訂閱很多 Observable,只要其中一個資料流完成就會去跑下個資料流,因此主要是用在寫入結果比讀取結果來得重要的情形之中。

combineLatest​

combineLastest 可以基於多個資料流的資料去建立新的資料流,它會取得其他資料流最新的資料,然後組合在一起變成新的資料流。值得注意的是,每個訂閱的資料流都要有資料產生,才會產生新的資料流,而再之後,每次訂閱的資料流進行更新,都會產生新資料流都會有新的資料出來。

combineLastest((x, y) => "" + x + y);

startWith, distinctUntilChanged, debounceTime​

這三個 operators 很常用在表單上面,startWith 會將傳出的資料第一筆加上特定值,distinctUntilChanged 和 debounceTime 很常用在 input,當使用者輸入時,會有很多資料湧進來,透過 debounceTime(10) 可以設置 10 毫秒的延遲,才會去做資料更新,distinctUntilChanged 的話可以辨別這次資料和上次資料有不同的話,才會去做資料更新。

依照該 youtuber 的例子,稍微做點改寫,有個搜尋欄,會每 300 毫秒且資料不同時再送出 API 請求,而在使用者還沒有輸入的時候,預設值會是 new-

const search$ = searchFormControl.valueChanges.pipe(
debounceTime(300),
distinctUntilChange(),
startWith("new-")
);

catchError, EMPTY​

catchError 是當資料流發生錯誤的時候,做的錯誤處理,而 EMPTY 可以配合 catchError,將資料流歸零的感覺,下次資料進來時仍可以持續運作。

this.http
.get<RedditRes>(
`https://www.reddit.com/r/${subreddit}/${sort}/.json?limit=100` +
(after ? `&after=${after}` : '')
).pipe(
catchError(() => EMPTY),
map((res) => (
// ...
))
)

這邊做個紀錄,catchError 也可以配合 retryWhen 去訂閱另一個資料流,再去重新跑一次資料

public getClients() {
return collectionData(clientsCollection, { idField: 'id' }).pipe(
catchError((err) => concat(of(null), throwError(err))),
retryWhen((errors) =>
errors.pipe(
switchMap(() =>
this.authService.getLoggedIn().pipe(filter(user) => !!user))
)
)
)
}

結論​

RxJS 有許多 operators 讓我們去操作資料流,達到更為複雜的使用者互動與前端畫面的呈現,這種程式的寫法被稱作為 reactive programming,也就是比較偏向 function programming 的寫法,和我們一般寫的 imperative programming 相比,來得比較不那麼直覺。關於 RxJS 的學習資源,我覺得官方文件有許多介紹,特別的是有些小遊戲的實作,像是 Alphabet Invasion Game ,之後會想要從跟著文件的小遊戲去學習更多操作。

參考資料​