简体   繁体   English

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

[英]How to debounce async validator in Angular 4 with RxJS observable?

I'm using custom async validator with Angular 4 reactive forms to check if E-Mail address is already taken by calling a backend.我正在使用带有 Angular 4 反应式表单的自定义异步验证器来检查电子邮件地址是否已通过调用后端获取。

However, Angular calls the validator, which makes request to the server for every entered character.但是,Angular 会调用验证器,它会为每个输入的字符向服务器发出请求。 This creates an unnecessary stress on the server.这会给服务器带来不必要的压力。

Is it possible to elegantly debounce async calls using RxJS observable?是否可以使用 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))
    ;
  }

}

While @Slava's answer is right.虽然@Slava 的回答是正确的。 It is easier with Observable :使用 Observable 更容易:

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

updated with modern RxJS:使用现代 RxJS 更新:

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

Notes:笔记:

  • Angular will automatically unsubscribe the returned Observable Angular 会自动取消订阅返回的Observable
  • timer() with one argument will only emit one item带一个参数的timer()只会发出一项
  • since timer emits only one value it does not matter if we use switchMap or flatMap因为timer只发出一个值,所以我们使用switchMap还是flatMap都没有关系
  • you should consider to use catchError in case that the server call fails如果服务器调用失败,您应该考虑使用catchError
  • angular docs: async-validation角度文档:异步验证

UPDATE RxJS 6.0.0:更新 RxJS 6.0.0:

 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)) ); }) ) }


*RxJS 5.5.0 *RxJS 5.5.0

For everyone who is using RxJS ^5.5.0 for better tree shaking and pipeable operators对于使用 RxJS ^5.5.0 以获得更好的摇树和可管道操作符的每个人

 import {of} from 'rxjs/observable/of'; import {map, switchMap} from 'rxjs/operators'; import {TimerObservable} from 'rxjs/observable/TimerObservable'; return (control: AbstractControl): Observable<ValidationErrors> => { return TimerObservable(500).pipe( switchMap(() => { if (!control.value) { return of(null) } return this.usersRepository.emailExists(control.value).pipe( map(result => (result ? { duplicateEmail: true } : null)) ); }) ) }

After studying some offered solutions with Observables I found them too complex and decided to use a solution with promises and timeouts.在研究了一些使用 Observables 提供的解决方案后,我发现它们太复杂了,并决定使用带有承诺和超时的解决方案。 Although blunt, this solution is much simpler to comprehend:虽然生硬,但这个解决方案更容易理解:

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);
      });

    }

  }

}

Here, I'm converting existing observable to promise using toPromise() operator of RxJS.在这里,我使用toPromise()运算符将现有的可观察对象转换为 promise。 Factory function is used because we need a separate timer for each control.使用工厂函数是因为我们需要为每个控件设置一个单独的计时器。


Please consider this a workaround.请将此视为一种解决方法。 Other solutions, which actually use RxJS, are most welcome!其他实际使用 RxJS 的解决方案是最受欢迎的!

I think your method only delay, not debounce, then find the sample way to archive this result.我认为您的方法只是延迟,而不是去抖动,然后找到存档此结果的示例方法。

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);
  })
}

If you want to implement it using RxJs,you can listen for valueChanges explicitly and apply async validator on it.如果你想使用 RxJs 来实现它,你可以显式地监听 valueChanges 并在它上面应用异步验证器。 For eg,considering you have reference ref to your abstractControl you can do,例如,考虑到您可以参考您的抽象控件,

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))
        })

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM