【Debug】記錄一個ng-autocomplete元件清除資料後沒有被驗證器攔截的問題

LYX6666發表於2022-05-23

零、問題復現

Debug類文章較難寫的地方在於,如何讓沒有接觸過這個專案的讀者明白當前在做什麼,明白這個功能的需求、文中的問題是怎麼出現的、以及怎麼改掉。因此記錄排查過程的文章最後的效果往往不如教程類文章。如果能讓沒寫過這段程式碼的讀者都能看懂文章,說明一篇文章寫的成功了。

May-23-2022 17-13-23.gif

如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。

Pasted image 20220523173321.png

瞭解了結構之後開始找問題,首先在最外層元件提交資料時開啟Console.log,列印日誌:

Pasted image 20220523173543.png

經過測試發現,想要情況輸入框,有兩種方式,一是點選右面的X號,二是手動把資訊刪掉,如GIF:

May-23-2022 17-37-14.gif
May-23-2022 17-37-53.gif

刪除後提交,這兩種方式的結果是不同的:

(1)如果點選X號刪除,輸出的資訊還是原來的資訊,可以猜測根本就沒有刪除資料:
Pasted image 20220523173941.png

(2)如果是手動清除資訊,就更有意思了,輸出的資訊就是空字串,但是儲存成功了,而且驗證器沒有攔截下來:
Pasted image 20220523174046.png

所以這表面上是一個BUG實際上是兩個。

二、解決問題

我們先來解決第一個問題:為什麼點選X號之後資料沒有被清除。
要思考這個問題,先要知道這兩個X號是誰控制的:
Pasted image 20220523174310.png
通過逐級的尋找,並沒有相關的HTML程式碼,因此斷定,這是內部的ng-autocomplete實現的。

Pasted image 20220523174548.png
通過程式碼可以容易的看出資料如何彈射的,資料改變時觸發onChangeSearch($event)進行更新,選擇下拉選單時觸發selectEvent($event)進行更新。
由剛才的GIF不難看出,點選X號清除時,這兩個事件都不會觸發,因此沒有彈射任何資料,最外層元件才會正常提交。

我們可以推測,既然有“改變”、“選擇”事件,也一定會有“清空”事件,檢視內部類,果然有:

Pasted image 20220523174925.png

這個inputCleared()就是清空時觸發的方法,因此我們只需要加上一行,並且傳一個空字串作為event,就能實現清空時更新空的資料了:

Pasted image 20220523175456.png

現在再看輸出結果就是null了:
Pasted image 20220523175530.png

現在清空按鈕已經可以實現了。

接下來解決這個更有意思的東西:為什麼清空了資料還是能提交?

起初我天真的以為:因為某個地方寫的有問題,導致Validator驗證器在子元件裡不生效了,於是我給所有子元件的FormControl全加上驗證器,但這根本沒用(也有點用,暴露了自己的無知)。

就在我以為就要卡住的時候,突然想起來一個細節:
如果是新增車輛時,元件剛初始化的時候,不輸入資料是不可以儲存的,說明驗證器是工作的。
Pasted image 20220523180043.png
但只要輸入了任何資料,儲存按鈕就會變亮,之後無論怎麼清空,都不會變暗了:
May-23-2022 18-01-54.gif

這讓我想到了,會不會是彈射的值出現了問題?

再看一眼控制檯列印的資料我才恍然大悟:
Pasted image 20220523180422.png
品牌欄位的型別物件!物件的name為空不代表物件為空!因此也不可能觸發驗證器!

於是找到了彈射的處理邏輯,果然和預想一致,空字串時也會賦值物件:
Pasted image 20220523180640.png

直接給它加一層判斷邏輯:如果是空字串,直接賦值Null,其他不變:
Pasted image 20220523181041.png

再次重新整理,功能已經實現了:
May-23-2022 18-12-05.gif

到此Debug完成。

三、總結

一開始只把關注點放到驗證器上,卻沒有線索。
後來靜下心來思考,才發現是物件的問題。
DEBUG是一項需要沉住氣的工作,要有耐心,不要浮躁。
(今天把論文終稿交了,奈斯!)

相關文章