零、問題復現
Debug類文章較難寫的地方在於,如何讓沒有接觸過這個專案的讀者明白當前在做什麼,明白這個功能的需求、文中的問題是怎麼出現的、以及怎麼改掉。因此記錄排查過程的文章最後的效果往往不如教程類文章。如果能讓沒寫過這段程式碼的讀者都能看懂文章,說明一篇文章寫的成功了。
如GIF所示,本來是一個普通的表單,除最後一個“停車位號碼”欄位外,所有欄位均新增了非空驗證器。
為了讓“車主”和“車輛品牌”兩個欄位可以自動彈出候選資訊,使用了Angular自動完成元件。
此處的BUG在於,這兩個欄位在清空資料後仍然可以提交,而且提交時仍是清空之前的資訊。
接下來記錄一下排查過程。
一、分析元件
為了簡化描述,以車輛品牌為例。
首先根據路由定位到元件,找到車輛品牌的欄位:
// 車輛編輯元件
// 車輛品牌的輸入框
<div class="mb-3 row">
<label class="col-sm-2 col-form-label text-right">車輛品牌<code>*</code></label>
<div class="col-sm-10">
<app-vehicle-brand-auto-complete [formControlName]="formKeys.vehicleBrand"></app-vehicle-brand-auto-complete>
</div></div>
通過app-vehicle-brand-auto-complete
可以看到,元件進行了封裝,並傳入了formControl,我們需要再找到封裝的元件。
// app-vehicle-brand-auto-complete元件
<yz-auto-complete [formControl]="formControl" [items]="items"
(doSearchKeyChange)="onSearchKeyChange($event)"></yz-auto-complete>
只有一行程式碼,它又進行了封裝,傳入formControl和items(車輛品牌的列表),
所以我們還需要再找到yz-auto-complete
元件:
// yz-auto-complete元件
<div class="ng-autocomplete">
<ng-autocomplete (inputChanged)='onChangeSearch($event)'
(selected)='selectEvent($event)'
[data]="items"
[debounceTime]="500"
[itemTemplate]="itemTemplate"
[ngModel]="formControl.value"
[searchKeyword]="keyword">
</ng-autocomplete> <ng-template #itemTemplate let-item>
<a [innerHTML]="item.name"></a>
</ng-template></div>
這一次終於看到Angular內建元件了。
概括一下就是,這個表單使用了多層套娃,並向內傳入表單欄位,最內層使用的是Angular內建的ng-autocomplete。
瞭解了結構之後開始找問題,首先在最外層元件提交資料時開啟Console.log,列印日誌:
經過測試發現,想要情況輸入框,有兩種方式,一是點選右面的X號,二是手動把資訊刪掉,如GIF:
刪除後提交,這兩種方式的結果是不同的:
(1)如果點選X號刪除,輸出的資訊還是原來的資訊,可以猜測根本就沒有刪除資料:
(2)如果是手動清除資訊,就更有意思了,輸出的資訊就是空字串,但是儲存成功了,而且驗證器沒有攔截下來:
所以這表面上是一個BUG實際上是兩個。
二、解決問題
我們先來解決第一個問題:為什麼點選X號之後資料沒有被清除。
要思考這個問題,先要知道這兩個X號是誰控制的:
通過逐級的尋找,並沒有相關的HTML程式碼,因此斷定,這是內部的ng-autocomplete
實現的。
通過程式碼可以容易的看出資料如何彈射的,資料改變時觸發onChangeSearch($event)
進行更新,選擇下拉選單時觸發selectEvent($event)
進行更新。
由剛才的GIF不難看出,點選X號清除時,這兩個事件都不會觸發,因此沒有彈射任何資料,最外層元件才會正常提交。
我們可以推測,既然有“改變”、“選擇”事件,也一定會有“清空”事件,檢視內部類,果然有:
這個inputCleared()
就是清空時觸發的方法,因此我們只需要加上一行,並且傳一個空字串作為event,就能實現清空時更新空的資料了:
現在再看輸出結果就是null了:
現在清空按鈕已經可以實現了。
接下來解決這個更有意思的東西:為什麼清空了資料還是能提交?
起初我天真的以為:因為某個地方寫的有問題,導致Validator驗證器在子元件裡不生效了,於是我給所有子元件的FormControl全加上驗證器,但這根本沒用(也有點用,暴露了自己的無知)。
就在我以為就要卡住的時候,突然想起來一個細節:
如果是新增車輛時,元件剛初始化的時候,不輸入資料是不可以儲存的,說明驗證器是工作的。
但只要輸入了任何資料,儲存按鈕就會變亮,之後無論怎麼清空,都不會變暗了:
這讓我想到了,會不會是彈射的值出現了問題?
再看一眼控制檯列印的資料我才恍然大悟:
品牌欄位的型別物件!物件的name為空不代表物件為空!因此也不可能觸發驗證器!
於是找到了彈射的處理邏輯,果然和預想一致,空字串時也會賦值物件:
直接給它加一層判斷邏輯:如果是空字串,直接賦值Null,其他不變:
再次重新整理,功能已經實現了:
到此Debug完成。
三、總結
一開始只把關注點放到驗證器上,卻沒有線索。
後來靜下心來思考,才發現是物件的問題。
DEBUG是一項需要沉住氣的工作,要有耐心,不要浮躁。
(今天把論文終稿交了,奈斯!)