如何在异步验证器中使用debounceTime()和distinctUntilChanged()

时间:2019-05-30 15:36:49

标签: javascript angular typescript angular-forms

我想在我的异步验证器中添加debounceTimedistinctUntilChanged

mockAsyncValidator(): AsyncValidatorFn {
    return (control: FormControl): Observable<ValidationErrors | null> => {
      return control.valueChanges.pipe(
        debounceTime(500),
        distinctUntilChanged(),
        switchMap(value => {
          console.log(value);  // log works here
          return this.mockService.checkValue(value).pipe(response => {
            console.log(response);  // log did not work here
            if (response) {
              return { invalid: true };
            }
            return null;
          })
        })
      );
  }

上面的代码不起作用,表单状态变为PENDING
但是,当我在this answer中使用timer时,代码可以工作,但是我不能使用distinctUntilChanged

return timer(500).pipe(
    switchMap(() => {
      return this.mockService.checkValue(control.value).pipe(response => {
        console.log(response);  // log works here
        if (response) {
          return { invalid: true };
        }
        return null;
      })
    })
  );

我尝试像使用BehaviorSubject

debouncedSubject = new BehaviorSubject<string>('');

并在AsyncValidatorFn中使用它,但仍然无法正常工作,就像这样:

this.debouncedSubject.next(control.value);
return this.debouncedSubject.pipe(
  debounceTime(500),
  distinctUntilChanged(), // did not work
                          // I think maybe it's because of I next() the value
                          // immediately above
                          // but I don't know how to fix this
  take(1), // have to add this, otherwise, the form is PENDING forever
           // and this take(1) cannot add before debounceTime()
           // otherwise debounceTime() won't work
  switchMap(value => {
    console.log(value); // log works here
    return this.mockService.checkValue(control.value).pipe(response => {
        console.log(response);  // log works here
        if (response) {
          return { invalid: true };
        }
        return null;
      }
    );
  })
);

1 个答案:

答案 0 :(得分:1)

问题在于,每当您在validatorFn中调用pipe()时,每次执行validatorFn时都会建立一个新管道。先前的值无法捕获,因此无法辨别或消除抖动。您可以做的是在外部设置两个BehaviourSubjects,在我的情况下是termDebouncervalidationEmitter

您可以设置工厂方法来创建此验证器,然后重新使用它。您还可以扩展AsyncValidator并使用DI设置创建一个类。我将在下面显示工厂方法。

export function AsyncValidatorFactory(mockService: MockService) { 
  const termDebouncer = new BehaviorSubject('');
  const validationEmitter = new BehaviorSubject<T>(null);
  let prevTerm = '';
  let prevValidity = null;

  termDebouncer.pipe(
        map(val => (val + '').trim()),
        filter(val => val.length > 0),
        debounceTime(500),
        mergeMap(term => { const obs = term === prevTerm ? of(prevValidity) : mockService.checkValue(term);
          prevTerm = term; 
          return obs; }),
        map(respose => { invalid: true } : null),
        tap(validity => prevValidity = validity)
    ).subscribe(validity => validationEmitter.next(validity))


  return (control: AbstractControl) => {
    termDebouncer.next(control.value)
    return validationEmitter.asObservable().pipe(take(2))
  }
}

编辑:此代码摘录来自Angular表单验证之外的用例(准确地说,请使用反应搜索小部件。)管道操作员可能需要进行更改以适合您的用例。

Edit2 take(1)first(),以确保可观察对象在发出验证消息后完成。 asObservable()将确保在下一次调用时将生成新的可观察值。您还可以跳过asObservable(),而仅跳过pipe(),因为管道运算符会分支异步管道并从那里开始创建一个新的可观察对象。您可能必须使用take(2)来超越behaviourSubject是有状态的并且拥有值的事实。

Edit3 :使用合并映射来处理distinctUntilChanged()将导致可观察对象不发射也不完整的事实。