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简易缓动插件(源码打包)
Feb 16 Javascript
jquery 实现窗口的最大化不论什么情况
Sep 03 Javascript
JavaScript拆分字符串时产生空字符的解决方案
Sep 26 Javascript
node.js中的querystring.escape方法使用说明
Dec 10 Javascript
JavaScript中利用各种循环进行遍历的方式总结
Nov 10 Javascript
继续学习javascript闭包
Dec 03 Javascript
阻止表单提交按钮多次提交的完美解决方法
May 16 Javascript
基于jQuery的ajax方法封装
Jul 14 Javascript
浅谈ajax请求不同页面的微信JSSDK问题
Feb 26 Javascript
详解Vue项目在其他电脑npm run dev运行报错的解决方法
Oct 29 Javascript
vue项目中将element-ui table表格写成组件的实现代码
Jun 12 Javascript
tracking.js实现前端人脸识别功能
Apr 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 stream_context_create()作用和用法分析
2011/03/29 PHP
PHP 常用的header头部定义汇总
2015/06/19 PHP
控制页面按钮在后台执行期间不重复提交的JS方法
2013/06/24 Javascript
JavaScript字符串对象substring方法入门实例(用于截取字符串)
2014/10/17 Javascript
JS实现点击文字对应DIV层不停闪动效果的方法
2015/03/02 Javascript
Javascript设计模式之观察者模式(推荐)
2016/03/29 Javascript
jQuery滚动新闻实现代码
2016/06/26 Javascript
Javascript iframe交互并兼容各种浏览器的解决方法
2016/07/12 Javascript
微信小程序 Canvas增强组件实例详解及源码分享
2017/01/04 Javascript
如何用JS/HTML将时间戳转换为“xx天前”的形式
2017/02/06 Javascript
[03:56]还原FTP电影首映式 DOTA2群星拼出遗迹世界
2014/03/26 DOTA
[42:20]2014 DOTA2华西杯精英邀请赛5 24 DK VS NewBee
2014/05/25 DOTA
[46:55]LGD vs Liquid 2019国际邀请赛小组赛 BO2 第一场 8.16
2019/08/19 DOTA
python 生成目录树及显示文件大小的代码
2009/07/23 Python
Python实现扫描指定目录下的子目录及文件的方法
2014/07/16 Python
Python进行数据科学工作的简单入门教程
2015/04/01 Python
在Django框架中设置语言偏好的教程
2015/07/27 Python
Python selenium文件上传方法汇总
2020/11/19 Python
Python 运行.py文件和交互式运行代码的区别详解
2019/07/02 Python
Python 基于wxpy库实现微信添加好友功能(简洁)
2019/11/29 Python
Python 爬取必应壁纸的实例讲解
2020/02/24 Python
利用python中的matplotlib打印混淆矩阵实例
2020/06/16 Python
Python 爬虫的原理
2020/07/30 Python
使用Html5实现异步上传文件,支持跨域,带有上传进度条
2016/09/17 HTML / CSS
德国体育用品网上商店:SC24.com
2016/08/01 全球购物
一站式跨境收款解决方案:Payoneer(派安盈)
2018/09/06 全球购物
英国在线潜水商店:Simply Scuba
2019/03/25 全球购物
波兰在线运动商店:YesSport
2020/07/23 全球购物
优秀的2014年两会精神解读
2014/03/17 职场文书
汽修专业自荐信
2014/07/07 职场文书
雷锋式好少年事迹材料
2014/08/17 职场文书
社团活动总结格式
2014/08/29 职场文书
2014年城管个人工作总结
2014/12/08 职场文书
领导欢送会主持词
2015/07/06 职场文书
MySQL中utf8mb4排序规则示例
2021/08/02 MySQL
Python查找算法的实现 (线性、二分,分块、插值查找算法)
2022/04/24 Python