如何使用RxJS observable去抖动Angular 4中的异步验证器?

时间:2017-05-25 15:10:42

标签: angular validation asynchronous rxjs

我正在使用带有Angular 4被动表单的自定义异步验证器来检查是否已经通过调用后端来获取电子邮件地址。

但是,Angular会调用验证器,验证器会为每个输入的字符向服务器发出请求。这会在服务器上造成不必要的压力。

是否可以使用RxJS observable优雅地去抖异步调用?

import {Observable} from 'rxjs/Observable';

import {AbstractControl, ValidationErrors} from '@angular/forms';
import {Injectable} from '@angular/core';

import {UsersRepository} from '../repositories/users.repository';


@Injectable()
export class DuplicateEmailValidator {

  constructor (private usersRepository: UsersRepository) {
  }

  validate (control: AbstractControl): Observable<ValidationErrors> {
    const email = control.value;
    return this.usersRepository
      .emailExists(email)
      .map(result => (result ? { duplicateEmail: true } : null))
    ;
  }

}

5 个答案:

答案 0 :(得分:17)

虽然@Slava的答案是对的。使用Observable更容易:

return (control: AbstractControl): Observable<ValidationErrors> => {
      return Observable.timer(this.debounceTime).switchMap(()=>{
        return this.usersRepository
            .emailExists(control.value)
            .map(result => (result ? { duplicateEmail: true } : null));
      });
}

如果新值到达,返回的Observable将被取消订阅,则无需手动管理超时。

答案 1 :(得分:8)

更新RxJS 6.0.0:

&#13;
&#13;
CategoryComparer
&#13;
&#13;
&#13;

* RxJS 5.5.0

对于使用RxJS ^ 5.5.0以获得更好的树摇动和可管理运算符的每个人

&#13;
&#13;
import {of, timer} from 'rxjs';
import {map, switchMap} from 'rxjs/operators';


return (control: AbstractControl): Observable<ValidationErrors> => {
  return timer(500).pipe(
    switchMap(() => {
      if (!control.value) {
        return of(null)
      }
                      
      return this.usersRepository.emailExists(control.value).pipe(
        map(result => (result ? { duplicateEmail: true } : null))
      );
    })
  )
}
&#13;
&#13;
&#13;

答案 2 :(得分:4)

在研究了Observables提供的一些解决方案后,我发现它们过于复杂,并决定使用具有承诺和超时的解决方案。虽然直率,但这个解决方案更容易理解:

import 'rxjs/add/operator/toPromise';

import {AbstractControl, ValidationErrors} from '@angular/forms';
import {Injectable} from '@angular/core';

import {UsersRepository} from '../repositories/users.repository';


@Injectable()
export class DuplicateEmailValidatorFactory {

  debounceTime = 500;


  constructor (private usersRepository: UsersRepository) {
  }

  create () {

    let timer;

    return (control: AbstractControl): Promise<ValidationErrors> => {

      const email = control.value;

      if (timer) {
        clearTimeout(timer);
      }

      return new Promise(resolve => {
        timer = setTimeout(() => {
          return this.usersRepository
            .emailExists(email)
            .map(result => (result ? { duplicateEmail: true } : null))
            .toPromise()
            .then(resolve)
          ;
        }, this.debounceTime);
      });

    }

  }

}

在这里,我正在使用RxJS的toPromise()运算符将现有的observable转换为promise。使用工厂功能是因为我们需要为每个控件使用单独的计时器。

请考虑这是一种解决方法。其他实际使用RxJS的解决方案是最受欢迎的!

答案 3 :(得分:0)

我认为你的方法只是延迟,而不是去抖,然后找到样本方法来存档这个结果。

import { debounce } from 'lodash';

...

constructor() {
   this.debounceValidate = debounce(this.debounceValidate.bind(this), 1000);
}

debounceValidate(control, resolve) {
   ...//your validator
}

validate (control: AbstractControl): Promise {
  return new Promise(resolve => {
    this.debounceValidate(control, resolve);
  })
}

答案 4 :(得分:-2)

如果要使用RxJ实现它,可以显式侦听valueChanges并在其上应用异步验证器。 例如,考虑到你可以参考你的abstractControl,

ref.valueChanges.debounceTime(500).subscribe(//value is new value of control
 value=>{this.duplicateValidator.validate(value)//duplicateValidator is ref to validator
                                .then(d => console.log(d))
                                .catch(d=>console.log(d))
        })