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 相关文章推荐
JavaScript实现维吉尼亚(Vigenere)密码算法实例
Nov 22 Javascript
获取中文字符串的实际长度代码
Jun 05 Javascript
node.js中的fs.lstat方法使用说明
Dec 16 Javascript
详解前端自动化工具gulp自动添加版本号
Dec 20 Javascript
Bootstrap风格的zTree右键菜单
Feb 17 Javascript
jquery append与appendTo方法比较
May 24 jQuery
微信小程序删除处理详解
Aug 16 Javascript
vue filters的使用详解
Jun 11 Javascript
Vue之beforeEach非登录不能访问的实现(代码亲测)
Jul 18 Javascript
小程序两种滚动公告栏的实现方法
Sep 17 Javascript
ionic3双击返回退出应用的方法
Sep 17 Javascript
Weex开发之地图篇的具体使用
Oct 16 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中使用接口实现工厂设计模式的代码
2012/06/17 PHP
CI框架给视图添加动态数据
2014/12/01 PHP
Laravel5中防止XSS跨站攻击的方法
2016/10/10 PHP
Yii2框架中使用PHPExcel导出Excel文件的示例
2017/08/09 PHP
jQuery插件windowScroll实现单屏滚动特效
2015/07/14 Javascript
jquery插件pagination实现无刷新ajax分页
2015/09/30 Javascript
js文本框输入内容智能提示效果
2015/12/02 Javascript
js手机号批量滚动抽奖实现代码
2020/04/17 Javascript
微信小程序之picker日期和时间选择器
2017/02/09 Javascript
Vue 单文件中的数据传递示例
2017/03/21 Javascript
Javascript中this关键字指向问题的测试与详解
2017/08/11 Javascript
vue父组件异步获取数据传给子组件的方法
2018/07/26 Javascript
vue项目中使用fetch的实现方法
2019/04/25 Javascript
js实现图片粘贴到网页
2019/12/06 Javascript
js实现无限层级树形数据结构(创新算法)
2020/02/27 Javascript
vuex(vue状态管理)的特殊应用案例分享
2020/03/03 Javascript
vue v-model的用法解析
2020/10/19 Javascript
Angular处理未可知异常错误的方法详解
2021/01/17 Javascript
[46:47]2014 DOTA2国际邀请赛中国区预选赛 DT VS HGT
2014/05/22 DOTA
[03:54]Ehome出征西雅图 回顾2016国际邀请赛晋级之路
2016/08/02 DOTA
Python基于win32ui模块创建弹出式菜单示例
2018/05/09 Python
python实现简易内存监控
2018/06/21 Python
django 微信网页授权认证api的步骤详解
2019/07/30 Python
浅谈python出错时traceback的解读
2020/07/15 Python
CSS3 清除浮动的方法示例
2018/06/01 HTML / CSS
德国在线香料制造商:Gewürzland
2020/03/10 全球购物
英国Lookfantastic中文网站:护肤品美妆美发购物(英国直邮)
2020/04/27 全球购物
如何查看在weblogic中已经发布的EJB
2012/06/01 面试题
预备党员转正思想汇报
2014/01/12 职场文书
幼儿园三八妇女节活动方案
2014/03/11 职场文书
陈安之励志演讲稿
2014/08/21 职场文书
上班迟到检讨书范文
2015/05/06 职场文书
2015年秋季学校开学标语
2015/07/16 职场文书
百年校庆感言
2015/08/01 职场文书
2016年第二十五次全国助残日活动总结
2016/04/01 职场文书
Redis之RedisTemplate配置方式(序列和反序列化)
2022/03/13 Redis