angular异步验证器防抖实例详解


Posted in Javascript onMarch 31, 2022

背景:

当前输入框的formControl设置了异步验证器,会根据当前的值进行请求后台,判断数据库中是否存在。

angular异步验证器防抖实例详解

原版异步验证器:

vehicleBrandNameNotExist(): AsyncValidatorFn {
    return (control: AbstractControl): Observable<ValidationErrors | null> => {
      if (control.value === '') {
        return of(null);
      }
      return this.vehicleBrandService.existByName(control.value).pipe(map(exists => exists ? {vehicleBrandNameExist: true} : null));
    };
  }

但是测试下来发现,该异步验证器触发的太频繁了。输入框每输入一个字母都会对后台进行请求,不利于节省资源。

防抖节流

这个相关的操作叫做防抖和节流。什么是防抖和节流?有什么区别?

本质上是一种优化高频率执行代码的一种手段。

比如浏览器的鼠标点击,键盘输入等事件触发时,会高频率地调用绑定在事件上的回调函数,一定程度上影响着资源的利用。

为了优化,我们需要 防抖(debounce) 和 节流(throttle) 的方式来减少调用频率。

定义:

防抖: n 秒后在执行该事件,若在 n 秒内被重复触发,则重新计时

节流: n 秒内只运行一次,若在 n 秒内重复触发,只有一次生效

举个例子来说明:

乘坐地铁,过闸机时,每个人进入后3秒后门关闭,等待下一个人进入。

闸机开之后,等待3秒,如果中又有人通过,3秒等待重新计时,直到3秒后没人通过后关闭,这是防抖

闸机开之后,每3秒后准时关闭一次,间隔时间执行,这是节流

代码实现:

防抖操作恰好符合我们的需求。

这里仅是说明angular中formContorl异步验证器如何防抖的步骤:

1.创建(改写)异步验证器

vehicleBrandNameNotExist(): AsyncValidatorFn {
    return (control: AbstractControl): Observable<ValidationErrors | null> => {
      if (control.value === '') {
        return of(null);
      }
      return control.valueChanges.pipe(
        // 防抖时间,单位毫秒
        debounceTime(1000),
        // 过滤掉重复的元素
        distinctUntilChanged(),
        // 调用服务, 获取结果
        switchMap(value => this.vehicleBrandService.existByName(value)),
        // 对结果进行处理,null表示正确,对象表示错误
        map((exists: boolean) => (exists ? {vehicleBrandNameExist: true} : null)),
        // 每次验证的结果是唯一的,截断流
        first()
      )
    };
  }
  • 添加异步验证器
let formControl = new FormControl('', [], asyncValidate.vehicleBrandNameNotExist());

之后我们在v层在相关的标签上绑定该fromControl就可以了。

疑惑

相关操作到这里就结束了,能够正常使用了。

但是改写之后还有些疑惑。

原来的版本是这么使用的:

return this.vehicleBrandService.existByName(...)

改写后是这么使用的:

return control.valueChanges.pipe(...

改写后使用了valueChanges,也就是产生了一个observable,它使得每当控件的值在更改时,它都会发出一个事件。

那么,每次调用异步验证器之后,我们每次都用valueChanges,每次的observable是不是同一个?

于是我进行了测试:
原理:多次调用异步验证器,并缓存ovservable,如果不相同则输出 “不相等”

angular异步验证器防抖实例详解

测试结果:如图,只是在第一次初始化的时候输出了不相等,因为第一次observable为undefined, 在有值之后,多次调用异步验证器发现observabel始终是同一个

angular异步验证器防抖实例详解

first()的使用

之前也不理解first的使用,看学长的文章之后才明白,first()来避免多次地这样返回值。

angular异步验证器防抖实例详解

所以我们产生的observable一直处于pending状态,需要用first让它返回第一个值就好。

return control.valueChanges.pipe(
           first() 
)

单元测试

一个好的功能要有一个好的单元测试。

1 it('should create an instance', async () => {
 2   expect(asyncValidate).toBeTruthy();
 3   let formControl = new FormControl('', [], asyncValidate.vehicleBrandNameNotExist());
 4   formControl.setValue('重复车辆品牌');
 5    // 等待防抖结束
 6   await new Promise(resolve => setTimeout(resolve, 1000));

 7   getTestScheduler().flush();
 8   expect(formControl.errors.vehicleBrandNameExist).toBeTrue();
     ...
}));

原来的时候我写的单元测试说这样的,

等待防抖结束我用了await new Promise 以及setTimeout。执行到第8行的时候,让线程等待1秒。

经过老师指正之后,发现这样并不好。假如某个测试需要等待一个小时,那么我们的执行时间就需要1个小时,这显然是不现实的。

所以这里用到了fakeAsync;

fakeAsync;

fakeAsync,字面上就是假异步,实际上还是同步进行的。

使用tick()模拟时间的异步流逝。

官方测试代码:

angular异步验证器防抖实例详解

仿照测试代码:

我在tick()前后,打印了new Date(),也就是当时的时间,结果是什么呢?

angular异步验证器防抖实例详解

可以看到第一个打印了17:19:30,也就是当时测试的时间。

但是在tick(10000000)后,打印的时间是20:06:10, 达到了一个未来的时间。

并且,这两条语句几乎是同时打印的,也就是说,单元测试并没有让我们真的等待10000000ms。

angular异步验证器防抖实例详解

所以经过测试时候我们就可以使用tick(1000)和fakeAsync模拟防抖时间结束了。

it('should create an instance', fakeAsync( () => {
    expect(asyncValidate).toBeTruthy();
    let formControl = new FormControl('', [], asyncValidate.vehicleBrandNameNotExist());
    formControl.setValue('重复车辆品牌');
    // 等待防抖结束
    tick(1000);
    getTestScheduler().flush();
    expect(formControl.errors.vehicleBrandNameExist).toBeTrue();

  }));

题外

写后台的时候还遇到了一个错误:

angular异步验证器防抖实例详解

它说我color没有设置默认值,但是回去一看明明已经设置了。

angular异步验证器防抖实例详解

打了很多断点都没发现问题。

后来到数据库一看,好家伙,怎么有两个,一个是colour,一个是color.

angular异步验证器防抖实例详解

之后翻看之前提交的代码,发现是之前用的是color,后面换成了colour。

但是我jpa hibernate设置的是update,所以数据库对应执行的是更新,所以上次的字段并没有删除,这才导致了数据库有两个字段。之后删除其中一个了就没事了。

jpa:
    hibernate:
      ddl-auto: update

补充

后面谷歌之后发现了一种比较简洁也好理解的方法:

不用调用first()之类的操作符, 把timer()的返回值作为一个observable就可以了。

time的作用在这里:
https://rxjs-cn.github.io/lea...

简单来说就是当 timer 结束时发出一个值。

这个原理猜测可能是当timer还没有结束并重复调用异步验证器时,表单就不管这个timer了,转而关注新的。

当然只是猜测,有机会再补充,经过测试防抖功能是正常的。

export class VehicleBrandAsyncValidator {
  /**
   * 防抖时间
   */
  debounceTime = 1000;
  
  constructor(private vehicleBrandService: VehicleBrandService) { }

  /**
   * 验证方法,车辆品牌名称
   */
  vehicleBrandNameNotExist(): AsyncValidatorFn {
    return (control: AbstractControl): Observable<ValidationErrors | null> => {
      if (control.value === '') {
        return of(null);
      }
      return timer(this.debounceTime).pipe(
        // 调用服务, 获取结果
        switchMap(() => this.vehicleBrandService.existByName(control.value)),
        // 对结果进行处理,null表示正确,对象表示错误
        map((exists: boolean) => (exists ? {vehicleBrandNameExist: true} : null)),
      )
    };
  }
}

总结

到此这篇关于angular异步验证器防抖的文章就介绍到这了,更多相关angular异步验证器防抖内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Javascript 相关文章推荐
js弹出的对话窗口永远保持居中显示
Dec 15 Javascript
Jquery事件的连接使用示例
Jun 18 Javascript
js 剪切板的用法(clipboardData.setData)与js match函数介绍
Nov 19 Javascript
借助javascript代码判断网页是静态还是伪静态
May 05 Javascript
删除Javascript Object中间的key
Nov 18 Javascript
jQuery获取标签文本内容和html内容的方法
Mar 27 Javascript
jQuery中dom元素上绑定的事件详解
Apr 24 Javascript
JS实现的通用表单验证插件完整实例
Aug 20 Javascript
老司机带你解读jQuery插件开发流程
May 16 Javascript
微信小程序 Nginx环境配置详细介绍
Feb 14 Javascript
JS实现的tab页切换效果完整示例
Dec 18 Javascript
vue实现的网易云音乐在线播放和下载功能案例
Feb 18 Javascript
vue使用refs获取嵌套组件中的值过程
Mar 31 #Vue.js
vue ref如何获取子组件属性值
Mar 31 #Vue.js
vue如何使用模拟的json数据查看效果
vue+iview实现手机号分段输入框
Mar 25 #Vue.js
AngularJS实现多级下拉框
Mar 25 #Javascript
JavaScript实现两个数组的交集
Mar 25 #Javascript
angular4实现带搜索的下拉框
You might like
PHP采集利器 Snoopy 试用心得
2011/07/03 PHP
有关于PHP中常见数据类型的汇总分享
2014/01/06 PHP
php实现zip压缩文件解压缩代码分享(简单易懂)
2014/05/10 PHP
javascript中的几个运算符
2007/06/29 Javascript
JQuery 选项卡效果(JS与HTML的分离)
2010/04/01 Javascript
javascript实现单击和双击并存的方法
2014/12/13 Javascript
即将发布的jQuery 3 有哪些新特性
2016/04/14 Javascript
AngularJS变量及过滤器Filter用法分析
2016/11/22 Javascript
javascript判断回文数详解及实现代码
2017/02/03 Javascript
微信小程序 this和that详解及简单实例
2017/02/13 Javascript
原生js仿浏览器滚动条效果
2017/03/02 Javascript
vue + socket.io实现一个简易聊天室示例代码
2017/03/06 Javascript
基于Node的React图片上传组件实现实例代码
2017/05/10 Javascript
Angular实现模版驱动表单的自定义校验功能(密码确认为例)
2018/05/17 Javascript
jquery实现联想词搜索框和搜索结果分页的示例
2018/10/10 jQuery
Python实现一个服务器监听多个客户端请求
2018/04/12 Python
Python通过属性手段实现只允许调用一次的示例讲解
2018/04/21 Python
面向初学者的Python编辑器Mu
2018/10/08 Python
flask-restful使用总结
2018/12/04 Python
对python特殊函数 __call__()的使用详解
2019/07/02 Python
树莓派4B+opencv4+python 打开摄像头的实现方法
2019/10/18 Python
Python3 解决读取中文文件txt编码的问题
2019/12/20 Python
如何让PyQt5中QWebEngineView与JavaScript交互
2020/10/21 Python
英国工艺品购物网站:Minerva Crafts
2018/01/29 全球购物
吉尔德利巧克力公司:Ghirardelli Chocolate Company
2019/03/27 全球购物
美国翻新电子产品商店:The Store
2019/10/08 全球购物
俄罗斯隐形眼镜和眼镜在线商店:Cronos
2020/06/02 全球购物
历史学专业推荐信
2013/11/06 职场文书
一份婚庆公司创业计划书
2014/01/11 职场文书
电子商务助理求职自荐信
2014/04/10 职场文书
广播体操口号
2014/06/18 职场文书
贯彻落实“八项规定”思想汇报
2014/09/13 职场文书
初中英语教师个人工作总结
2015/02/09 职场文书
涨价通知
2015/04/23 职场文书
Python 数据结构之十大经典排序算法一文通关
2021/10/16 Python
深入理解pytorch库的dockerfile
2022/06/10 Python