【Debug】记录一个ng-autocomplete组件清除数据后没有被验证器拦截的问题

零、问题复现

Debug类文章较难写的地方在于,如何让没有接触过这个项目的读者明白当前在做什么,明白这个功能的需求、文中的问题是怎么出现的、以及怎么改掉。因此记录排查过程的文章最后的效果往往不如教程类文章。如果能让没写过这段代码的读者都能看懂文章,说明一篇文章写的成功了。

【Debug】记录一个ng-autocomplete组件清除数据后没有被验证器拦截的问题_第1张图片

如GIF所示,本来是一个普通的表单,除最后一个“停车位号码”字段外,所有字段均添加了非空验证器。
为了让“车主”和“车辆品牌”两个字段可以自动弹出候选信息,使用了Angular自动完成组件。
此处的BUG在于,这两个字段在清空数据后仍然可以提交,而且提交时仍是清空之前的信息。

接下来记录一下排查过程。

一、分析组件

为了简化描述,以车辆品牌为例。
首先根据路由定位到组件,找到车辆品牌的字段:

// 车辆编辑组件
// 车辆品牌的输入框

通过app-vehicle-brand-auto-complete可以看到,组件进行了封装,并传入了formControl,我们需要再找到封装的组件。

// app-vehicle-brand-auto-complete组件

只有一行代码,它又进行了封装,传入formControl和items(车辆品牌的列表),
所以我们还需要再找到yz-auto-complete组件:

// yz-auto-complete组件

这一次终于看到Angular内置组件了。

概括一下就是,这个表单使用了多层套娃,并向内传入表单字段,最内层使用的是Angular内置的ng-autocomplete。

【Debug】记录一个ng-autocomplete组件清除数据后没有被验证器拦截的问题_第2张图片

了解了结构之后开始找问题,首先在最外层组件提交数据时开启Console.log,打印日志:

【Debug】记录一个ng-autocomplete组件清除数据后没有被验证器拦截的问题_第3张图片

经过测试发现,想要情况输入框,有两种方式,一是点击右面的X号,二是手动把信息删掉,如GIF:

【Debug】记录一个ng-autocomplete组件清除数据后没有被验证器拦截的问题_第4张图片
【Debug】记录一个ng-autocomplete组件清除数据后没有被验证器拦截的问题_第5张图片

删除后提交,这两种方式的结果是不同的:

(1)如果点击X号删除,输出的信息还是原来的信息,可以猜测根本就没有删除数据:
【Debug】记录一个ng-autocomplete组件清除数据后没有被验证器拦截的问题_第6张图片

(2)如果是手动清除信息,就更有意思了,输出的信息就是空字符串,但是保存成功了,而且验证器没有拦截下来:
【Debug】记录一个ng-autocomplete组件清除数据后没有被验证器拦截的问题_第7张图片

所以这表面上是一个BUG实际上是两个。

二、解决问题

我们先来解决第一个问题:为什么点击X号之后数据没有被清除。
要思考这个问题,先要知道这两个X号是谁控制的:
【Debug】记录一个ng-autocomplete组件清除数据后没有被验证器拦截的问题_第8张图片
通过逐级的寻找,并没有相关的HTML代码,因此断定,这是内部的ng-autocomplete实现的。

【Debug】记录一个ng-autocomplete组件清除数据后没有被验证器拦截的问题_第9张图片
通过代码可以容易的看出数据如何弹射的,数据改变时触发onChangeSearch($event)进行更新,选择下拉菜单时触发selectEvent($event)进行更新。
由刚才的GIF不难看出,点击X号清除时,这两个事件都不会触发,因此没有弹射任何数据,最外层组件才会正常提交。

我们可以推测,既然有“改变”、“选择”事件,也一定会有“清空”事件,查看内部类,果然有:

【Debug】记录一个ng-autocomplete组件清除数据后没有被验证器拦截的问题_第10张图片

这个inputCleared()就是清空时触发的方法,因此我们只需要加上一行,并且传一个空字符串作为event,就能实现清空时更新空的数据了:

【Debug】记录一个ng-autocomplete组件清除数据后没有被验证器拦截的问题_第11张图片

现在再看输出结果就是null了:
【Debug】记录一个ng-autocomplete组件清除数据后没有被验证器拦截的问题_第12张图片

现在清空按钮已经可以实现了。

接下来解决这个更有意思的东西:为什么清空了数据还是能提交?

起初我天真的以为:因为某个地方写的有问题,导致Validator验证器在子组件里不生效了,于是我给所有子组件的FormControl全加上验证器,但这根本没用(也有点用,暴露了自己的无知)。

就在我以为就要卡住的时候,突然想起来一个细节:
如果是新增车辆时,组件刚初始化的时候,不输入数据是不可以保存的,说明验证器是工作的。
【Debug】记录一个ng-autocomplete组件清除数据后没有被验证器拦截的问题_第13张图片
但只要输入了任何数据,保存按钮就会变亮,之后无论怎么清空,都不会变暗了:
【Debug】记录一个ng-autocomplete组件清除数据后没有被验证器拦截的问题_第14张图片

这让我想到了,会不会是弹射的值出现了问题?

再看一眼控制台打印的数据我才恍然大悟:
【Debug】记录一个ng-autocomplete组件清除数据后没有被验证器拦截的问题_第15张图片
品牌字段的类型对象!对象的name为空不代表对象为空!因此也不可能触发验证器!

于是找到了弹射的处理逻辑,果然和预想一致,空字符串时也会赋值对象:
【Debug】记录一个ng-autocomplete组件清除数据后没有被验证器拦截的问题_第16张图片

直接给它加一层判断逻辑:如果是空字符串,直接赋值Null,其他不变:
【Debug】记录一个ng-autocomplete组件清除数据后没有被验证器拦截的问题_第17张图片

再次刷新,功能已经实现了:
【Debug】记录一个ng-autocomplete组件清除数据后没有被验证器拦截的问题_第18张图片

到此Debug完成。

三、总结

一开始只把关注点放到验证器上,却没有线索。
后来静下心来思考,才发现是对象的问题。
DEBUG是一项需要沉住气的工作,要有耐心,不要浮躁。

你可能感兴趣的:(angular)