在日常生活中,大家應該滿常看到有些系統的搜尋輸入框是可以在一邊打字的同時,一邊將搜尋結果呈現在一個下拉選單裡,非常地貼心且方便。
當然,這其中其實有很多細節,不過我們今天就專注在前端的表單開發上,來用 Reactive Forms 實作這個搜尋輸入框吧!
沒錯,就算只是個搜尋框,它也是個表單噢!
正好最近六角學院即將舉辦第三屆的前端 & UI 修煉精神時光屋的活動,這次它們與交通部合作,並提供了全國最大的
運輸資料流通服務平台 (TDX) 之交通 API 給大家使用,讓大家可以透過此活動精進自己的實力,非常推薦給大家。
想當初我第一次寫鐵人賽時,也是使用了參加六角舉辦的第一屆前端修煉精神時光屋的素材來寫,雖然這次沒有要參賽,但又跟六角有關係了呢!
總之,藉由這次的機會與交通部提供的 運輸資料流通服務平台 (TDX) 之交通 API ,我們來簡單地做一個可以查詢台北捷運的車站的搜尋輸入框吧!
這次因為有 API 可以使用的關係,會精實很多,如果跟不上的朋友,可能要再多熟悉一下 Angular 噢!
需求規格說明
簡單來說,這個功能會需要一個輸入框與一個表格,當使用者在輸入框裡打字時,表格的內容也會連動呈現出搜尋結果。
由於 Auto-Complete 的搜尋輸入框如果要自己做會需要處理不少細節,又不想安裝 UI 框架佔篇幅,所以我用這個方式來呈現查詢結果。
表格的欄位有以下這些:
- 車站代號
- 車站名稱
- 車站所屬縣市
- 車站所屬鄉鎮區
- 假日是否允許自行車進出站
- 位置
最後呈現結果:
實作開始
首先,如果在需求明確的情況下,我個人習慣會先把畫面準備好。
HTML 的部份大概會長這樣:
1 | <p><input type="text" placeholder="請輸入捷運站名稱" /></p> |
CSS 的部份大家就自行發揮囉!
畫面看起來會像這樣:
接著我們會需要一個 FormControl
來跟輸入框綁定,所以我們在 .ts
裡新增一個屬性 ─ searchingInputControl
:
1 | export class ReactiveFormsAutoCompleteSearchingComponent implements OnInit { |
別忘了先到
.module.ts
裡引入FormsModule
與ReactiveFormsModule
噢!
然後將 searchingInputControl
與畫面輸入框綁定:
1 | <p><input type="text" placeholder="請輸入捷運站名稱" [formControl]="searchingInputControl" /></p> |
接著我們使用昨天分享過的 valueChanges
來確認是否已正確綁定:
1 | export class ReactiveFormsAutoCompleteSearchingComponent implements OnInit { |
結果:
看起來已經有正確的跟搜尋輸入框綁定了,那接下來要怎麼做才好呢?
Service
我們的目的是希望使用者在輸入捷運站名稱的同時,只留下跟使用者的輸入有關聯的捷運站。
因此,我們會需要一支 Service 來幫我們呼叫交通部所提供的 運輸資料流通服務平台 (TDX) 之交通 API ,並把查詢結果顯示到畫面上。
Service 的程式碼大概會長這個樣子:
1 | @Injectable() |
上述程式碼中有以下幾個重點:
要呼叫 API 的話,需要先到
.module.ts
裡引入HttpClientModule
,才能在 Service 裡使用HttpClient
來呼叫 API。MetroStationDTO
是我根據交通部所提供的 運輸資料流通服務平台 (TDX) 之交通 API 裡定義的資料介面,詳細位置需先選擇「軌道」再點選「捷運」,如下圖所示:
由於 HTTP Method 是
GET
的緣故,所以參數是使用Query Parameters
的方式帶進 URL 之中。如果使用者沒有輸入站名時,還帶
$filter
參數會收到伺服器回傳的Bed Request
錯誤,因此增加一個判斷式 ─ 當傳入的stationName
為 Truthy 值時,才帶$filter
參數。參數
$filter
的值該怎麼帶這件事情其實在文件中沒有寫,算是這個文件比較美中不足的地方。好在六角學院的院長 ─ 廖洧杰院長前陣子有開直播課教學,而我猜測院長一定有在那堂課講這件事情,所以去翻了一下該堂直播課的共筆才找到該怎麼帶它的值。
Service 準備好之後,接下來就要將 FormControl
的 valueChanges
事件與 API 相結合了。
準備好
見證神蹟了嗎?
Operators
RxJS 真的是一個很棒的函式庫,它讓我們可以很好地操作非同步與資料串流,而且還能讓我們的程式碼非常地簡潔、非常地好閱讀。
就像我們現在需要把使用者的輸入事件與 API 做結合時,用 RxJS 的 Operators 就可以非常完美、漂亮地結合在一起。
就像這樣:
1 | export class ReactiveFormsAutoCompleteSearchingComponent implements OnInit { |
結果:
我相信在這邊一定會有非常多朋友看傻眼,這是什麼神操作?!這樣就接好了?!
沒錯!這樣就接好了,是不是比你想像中簡單非常多呢?
那這串到底做了什麼事呢?
首先,我希望這個畫面一開始的時候就會先查詢一次,所以我使用 startWith('')
來呼叫查詢 API 。
再者,我希望查詢的間隔不要太過快速,當使用者「可能」已經打完字的時候才查詢,所以我使用 debounceTime(500)
來讓查詢的時間點會在使用者停止打字 500 毫秒後才呼叫查詢 API。
最後,則要將原本是 valueChanges
的 Observable 轉換成 呼叫 API 的 Observable 這件事情 ,所以我使用 switchMap(value => this.service.searchStation(value))
。
關於
startWith
,大家可以參考官方文件或是 Mike 的文章。
AsyncPipe
接著,我們要將得到的資料綁定到畫面上,而綁定到畫面上的方式大致上有兩種:
- 自己訂閱後將資料指定給 Component 的屬性:
1 | export class ReactiveFormsAutoCompleteSearchingComponent implements OnInit { |
然後再綁到畫面上:
1 | <table> |
- 不要自己訂閱,先將 Observable 準備好並用 Component 的屬性儲存起來:
1 | export class ReactiveFormsAutoCompleteSearchingComponent { |
然後透過 AsyncPipe
讓 Template 自己訂閱:
1 | <table> |
就結果來說,這兩個方法基本上都可以,但我個人非常推薦使用第二種方式。
原因是使用第二種的方式一方面可以避免我們在 Component 被 Destroy 時忘記解除訂閱而導致 Memory Leak 的情形,另一方面是 Observable 會比單純資料好用很多。
甚至有時候我們自己訂閱會發生「明明資料就有收到但畫面沒有更新」的詭異狀況。
結果:
Other Pipes
雖然目前運作良好,但還有一些小東西還沒處理完:
- 假日是否允許自行車進出站的欄位我想讓它呈現
是
或是否
。 - 位置的欄位我想讓它以
latitude, longitude
的格式呈現。 - 連結我想要可以點擊後用新的頁籤打開 Google Map ,並會看到那個捷運站的位置。
以上這三個小東西非常地簡單,我想大家應該也都知道該怎麼做,但是既然都已經到了第二十四天了,這邊我覺得我們要使用 Pipe
,而不是像之前一樣直接寫在 Component 裡。
這是因為,如果像之前的 getErrorMessage
是寫在 Component 裡的話,其實當畫面渲染時,該函式就會被呼叫,不管該值有沒有被改變。
但是使用 Pipe
的話,在該值被改變前,是不會被呼叫第二次的。
再者,使用 Pipe
的話,重用性與可維護性也比較好。
所以我建議大家可以使用 Pipe
來完成最後的小調整。
我個人會建立三個 Pipe
─ BooleanInZhTwPipe
、 GoogleMapLinkPipe
與 LocationStringPipe
。
它們的程式碼如下:
1 | @Pipe({ |
1 | @Pipe({ |
1 | @Pipe({ |
最終結果:
本日小結
今天的重點主要是:
- 學習如何使用 TDX API 。
- 學習如何使用 RxJS 的 Operator ─
startWith
、debounceTime
與switchMap
將valueChanges
與呼叫 API 串聯。 - 學習如何使用 AsyncPipe 。
- 學習如何自定
Pipe
。
今天的練習對於一些剛學 Angular 的朋友來說會滿精實且資訊量有點大的,大家可以多看幾遍,多自己練習、做實驗,相信對大家來說會很有幫助。
關於 RxJS ,如果大家想知道更多資訊,我推薦大家去看 Mike 的打通 RxJS 任督二脈系列文,或者是直接買實體書也行。
雖然今天的實作已經完成了,但還有測試的部份,我們明天來撰寫它吧!
今天的程式碼會放在 Github - Branch: day24 上供大家參考,建議大家在看我的實作之前,先按照需求規格自己做一遍,之後再跟我的對照,看看自己的實作跟我的實作不同的地方在哪裡、有什麼好處與壞處,如此反覆咀嚼消化後,我相信你一定可以進步地非常快!
如果有任何的問題或是回饋,還請麻煩留言給我讓我知道!