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 相关文章推荐
JQuery下关于$.Ready()的分析
Dec 13 Javascript
jquery 获取标签名(tagName)示例代码
Jul 11 Javascript
button没写type=button会导致点击时提交
Mar 06 Javascript
按钮接受回车事件的三种实现方法
Jun 06 Javascript
使用js复制链接中的部分文字的方法
Jul 30 Javascript
理解javascript中try...catch...finally
Dec 25 Javascript
JavaScript+canvas实现七色板效果实例
Feb 18 Javascript
jQuery选择器及jquery案例详解(必看)
May 20 Javascript
JS对大量数据进行多重过滤的方法
Nov 04 Javascript
Node.js 中exports 和 module.exports 的区别
Mar 14 Javascript
微信小程序 图片绝对定位(背景图片)
Apr 05 Javascript
使用classList来实现两个按钮样式的切换方法
Jan 24 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 empty(),isset(),is_null()的实例测试详解
2013/06/06 PHP
php封装的数据库函数与用法示例【参考thinkPHP】
2016/11/08 PHP
PHP http请求超时问题解决方案
2020/11/13 PHP
jQuery查询数据返回object和字符串影响原因是什么
2013/08/09 Javascript
JQuery页面图片切换和新闻列表滚动效果的具体实现
2013/09/26 Javascript
js获取下拉列表的值和元素个数示例
2014/05/07 Javascript
让alert不出现弹窗的两种方法
2014/05/18 Javascript
jQuery实现3D文字特效的方法
2015/03/10 Javascript
jQuery实现可高亮显示的二级CSS菜单效果
2015/09/01 Javascript
JavaScript面向对象之私有静态变量实例分析
2016/01/14 Javascript
星期几的不同脚本写法(推荐)
2016/06/01 Javascript
jQuery UI制作选项卡(tabs)
2016/12/13 Javascript
js实现简单的计算器功能
2017/01/16 Javascript
jQuery实现分页功能(含ajax请求、后台数据、附完整demo)
2017/04/03 jQuery
你有必要知道的10个JavaScript难点
2017/07/25 Javascript
vue中使用ueditor富文本编辑器
2018/02/08 Javascript
详解无限滚动插件vue-infinite-scroll源码解析
2019/05/12 Javascript
javascript绘制简单钟表效果
2020/04/07 Javascript
详解Python nose单元测试框架的安装与使用
2017/12/20 Python
python保存网页图片到本地的方法
2018/07/24 Python
Python中的字符串切片(截取字符串)的详解
2019/05/15 Python
Python实现微信中找回好友、群聊用户撤回的消息功能示例
2019/08/23 Python
一款利用css3的鼠标经过动画显示详情特效的实例教程
2014/12/29 HTML / CSS
HTML5中如何显示视频呢 HTML5视频播放demo
2013/06/08 HTML / CSS
ONLY德国官方在线商店:购买时尚女装
2017/09/21 全球购物
初级软件工程师面试题 Junior Software Engineer Interview
2015/02/15 面试题
夜大毕业生自我鉴定
2013/10/31 职场文书
公司领导推荐信
2013/11/12 职场文书
信息专业个人的自我评价
2013/12/27 职场文书
医院实习接收函
2014/01/12 职场文书
文明美德伴我成长演讲稿
2014/05/12 职场文书
高校群众路线教育实践活动剖析材料
2014/10/10 职场文书
派出所副所长四风问题个人整改措施思想汇报
2014/10/13 职场文书
思想作风建设心得体会
2014/10/22 职场文书
2014年度工作总结报告
2014/12/15 职场文书
Dashboard管理Kubernetes集群与API访问配置
2022/04/01 Servers