零、问题复现
Debug类文章较难写的地方在于,如何让没有接触过这个项目的读者明白当前在做什么,明白这个功能的需求、文中的问题是怎么出现的、以及怎么改掉。因此记录排查过程的文章最后的效果往往不如教程类文章。如果能让没写过这段代码的读者都能看懂文章,说明一篇文章写的成功了。
如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。
了解了结构之后开始找问题,首先在最外层组件提交数据时开启Console.log,打印日志:
经过测试发现,想要情况输入框,有两种方式,一是点击右面的X号,二是手动把信息删掉,如GIF:
删除后提交,这两种方式的结果是不同的:
(1)如果点击X号删除,输出的信息还是原来的信息,可以猜测根本就没有删除数据:
(2)如果是手动清除信息,就更有意思了,输出的信息就是空字符串,但是保存成功了,而且验证器没有拦截下来:
所以这表面上是一个BUG实际上是两个。
二、解决问题
我们先来解决第一个问题:为什么点击X号之后数据没有被清除。
要思考这个问题,先要知道这两个X号是谁控制的:
通过逐级的寻找,并没有相关的HTML代码,因此断定,这是内部的ng-autocomplete
实现的。
通过代码可以容易的看出数据如何弹射的,数据改变时触发onChangeSearch($event)
进行更新,选择下拉菜单时触发selectEvent($event)
进行更新。
由刚才的GIF不难看出,点击X号清除时,这两个事件都不会触发,因此没有弹射任何数据,最外层组件才会正常提交。
我们可以推测,既然有“改变”、“选择”事件,也一定会有“清空”事件,查看内部类,果然有:
这个inputCleared()
就是清空时触发的方法,因此我们只需要加上一行,并且传一个空字符串作为event,就能实现清空时更新空的数据了:
现在清空按钮已经可以实现了。
接下来解决这个更有意思的东西:为什么清空了数据还是能提交?
起初我天真的以为:因为某个地方写的有问题,导致Validator验证器在子组件里不生效了,于是我给所有子组件的FormControl全加上验证器,但这根本没用(也有点用,暴露了自己的无知)。
就在我以为就要卡住的时候,突然想起来一个细节:
如果是新增车辆时,组件刚初始化的时候,不输入数据是不可以保存的,说明验证器是工作的。
但只要输入了任何数据,保存按钮就会变亮,之后无论怎么清空,都不会变暗了:
这让我想到了,会不会是弹射的值出现了问题?
再看一眼控制台打印的数据我才恍然大悟:
品牌字段的类型对象!对象的name为空不代表对象为空!因此也不可能触发验证器!
于是找到了弹射的处理逻辑,果然和预想一致,空字符串时也会赋值对象:
直接给它加一层判断逻辑:如果是空字符串,直接赋值Null,其他不变:
到此Debug完成。
三、总结
一开始只把关注点放到验证器上,却没有线索。
后来静下心来思考,才发现是对象的问题。
DEBUG是一项需要沉住气的工作,要有耐心,不要浮躁。