[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 ,之後會想要從跟著文件的小遊戲去學習更多操作。