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 相关文章推荐
基础的prototype.js常用函数及其用法
Mar 10 Javascript
jQuery之折叠面板的深入解析
Jun 19 Javascript
javaScript NameSpace 简单说明介绍
Jul 18 Javascript
iframe如何动态创建及释放其所占内存
Sep 03 Javascript
使用requestAnimationFrame实现js动画性能好
Aug 06 Javascript
一分钟理解js闭包
May 04 Javascript
jQuery源码分析之init的详细介绍
Feb 13 Javascript
ionic2自定义cordova插件开发以及使用(Android)
Jun 19 Javascript
JavaScript实现滑动导航栏效果
Aug 30 Javascript
微信小程序实现漂亮的弹窗效果
May 26 Javascript
js根据后缀判断文件文件类型的代码
May 09 Javascript
利用JavaScript为句子加标题的3种方法示例
Jan 05 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 Socket 编程模拟Http post和get请求
2014/11/25 PHP
PHP编程入门的基本语法知识点总结
2016/01/26 PHP
php微信公众平台开发(一) 配置接口
2016/12/06 PHP
Yii2――使用数据库操作汇总(增删查改、事务)
2016/12/19 PHP
支持ie与FireFox的剪切板操作代码
2009/09/28 Javascript
Webkit的跨域安全问题说明
2011/09/13 Javascript
jquery animate实现鼠标放上去显示离开隐藏效果
2013/07/21 Javascript
简介JavaScript中的sub()方法的使用
2015/06/08 Javascript
Jquery 全选反选实例代码
2015/11/19 Javascript
直接拿来用的页面跳转进度条JS实现
2016/01/06 Javascript
js只执行1次的函数示例
2016/07/20 Javascript
jQuery实现的瀑布流加载效果示例
2016/09/13 Javascript
jquery日历插件e-calendar升级版
2016/11/10 Javascript
bootstrap导航栏、下拉菜单、表单的简单应用实例解析
2017/01/06 Javascript
nodejs的压缩文件模块archiver用法示例
2017/01/18 NodeJs
vuejs使用FormData实现ajax上传图片文件
2017/08/08 Javascript
vue父组件中获取子组件中的数据(实例讲解)
2017/09/27 Javascript
js如何找出字符串中的最长回文串
2018/06/04 Javascript
快速解决vue-cli在ie9+中无效的问题
2018/09/04 Javascript
微信小程序中遇到的iOS兼容性问题小结
2018/11/14 Javascript
微信小程序云开发使用方法新手初体验
2019/05/16 Javascript
jQuery实现的记住帐号密码功能完整示例
2019/08/03 jQuery
[02:54]DOTA2英雄基础教程 撼地者
2014/01/14 DOTA
python读写二进制文件的方法
2015/05/09 Python
opencv3/C++ 平面对象识别&amp;透视变换方式
2019/12/11 Python
python如何提取英语pdf内容并翻译
2020/03/03 Python
Levi’s西班牙官方网站:李维斯,著名的牛仔裤品牌
2020/08/20 全球购物
如何整合JQuery和Prototype
2014/01/31 面试题
外贸业务员岗位职责
2013/11/24 职场文书
英语自我评价范文
2014/01/24 职场文书
作风大整顿心得体会
2014/09/10 职场文书
2015年元旦主持词开场白
2014/12/14 职场文书
平凡的世界读书笔记
2015/06/25 职场文书
学生会副主席竞选稿
2015/11/19 职场文书
2016年习总书记讲话学习心得体会
2016/01/20 职场文书
Windows下用Nginx配置https服务器及反向代理的问题
2021/09/25 Servers