【问题标题】:Angular 5 Debounce Custom Async ValidatorAngular 5 Debounce 自定义异步验证器
【发布时间】:2023-03-12 00:35:01
【问题描述】:

我创建了一个自定义异步验证器,它使用服务来验证服务器上的电子邮件。但是,这意味着每次输入一个字符时都会命中服务器,这是不好的。我在这里关注了几个我无法开始工作的答案。

我的验证者:

import {FormControl, NG_ASYNC_VALIDATORS, Validator} from 
'@angular/forms';
import { Http } from '@angular/http';
import {Directive, forwardRef} from "@angular/core";
import {ValidateEmailService} from "../services/validate-email.service";
import {UserService} from "../services/user.service";

@Directive({
  selector: '[appEmailValidator]',
  providers: [
    { provide: NG_ASYNC_VALIDATORS, useExisting: forwardRef(() => EmailValidator), multi: true }
  ]
})
export class EmailValidator implements Validator {
  public validateEmailService: ValidateEmailService;

  constructor(
    private _http: Http,
    private _userService: UserService
  ) {
    this.validateEmailService = new ValidateEmailService(this._http, this._userService);
  }

  validate(c: FormControl) {
    return new Promise(resolve => {
      this.validateEmailService.validateEmail(c.value)
        .subscribe((res) => {
          console.log(res);
          if (res.valid) {
            resolve(null);
          } else {
            resolve({
              valid: {
                valid: false
              }
            });
          }
        });
      })
    }
}

它本身运行良好,但一旦我尝试为其添加某种形式的去抖动,我最终会破坏它。

我已经尝试了this question 的答案,但我得到了Type X is not assignable to type 'Observable<any>' 等方面的错误。

我通过使用setTimeout 接近了,但最终所做的只是停止了该功能。

我的最终目标是仅在输入未更改大约 600 毫秒时才运行验证器,但会满足于每 600-2000 毫秒验证一次。

为了更加清楚,ValidateEmailService 中的 validateEmail 方法:

public validateEmail(email: string) {

  let validateEmail = new ValidateEmail(email);

  return this._http.get(
    this.getUrl(validateEmail),
    this.getOptionArgs())
    .map((response: Response) => Object.assign(new UserEmailVerification(), response.json().UserEmailVerification));

}

【问题讨论】:

    标签: angular typescript angular5


    【解决方案1】:

    我还没有看到将异步验证器实现为指令,而是作为分配给表单控件的验证器函数。

    这是我用于类似情况的示例验证器:

    import { Injectable } from '@angular/core';
    import { AbstractControl, AsyncValidatorFn } from '@angular/forms';
    import { Observable, timer, of } from 'rxjs';
    import { switchMap, map } from 'rxjs/operators';
    
    import { MembershipsService } from '@app/memberships/memberships.service';
    
    @Injectable()
    export class MembershipsValidators {
    
      constructor (
        private membershipsService: MembershipsService,
      ) {}
    
      checkMembershipExists(email?: string): AsyncValidatorFn {
        return (control: AbstractControl): Observable<{ [key: string]: any } | null> => {
          if (control.value === null || control.value.length === 0) {
            return of(null);
          }
          else if (email && email === control.value) {
            return of(null);
          }
          else {
            return timer(500).pipe(
              switchMap(() => {
                return this.membershipsService.lookupMember(control.value).pipe(
                  map(member => {
                    if (!member) {
                      return { noMembership: { value: control.value } };
                    }
    
                    return null;
                  })
                );
              })
            );
          }
        };
      }
    
    }
    

    这样导入并应用于表单控件:

    this.form = this.formBuilder.group({
      memberEmail: new FormControl('', {
        validators: [ Validators.required, Validators.pattern(regexPatterns.email) ],
        asyncValidators: [ this.membershipsValidators.checkMembershipExists() ],
      }),
    });
    

    这样,异步验证器在同步验证器得到满足之前不会触发。

    【讨论】:

    • 我对角度有点陌生,所以这可能是错误的,但指令是因为我在模板驱动的表单而不是反应表单上使用它。不幸的是,我暂时无法使用模板驱动的表单。
    • 我明白了。应该应用相同的方法。主要区别在于我返回的是一个可观察对象而不是一个承诺,但仅在 500 毫秒过去后。
    • 只是我的意见,但如果你有机会,请切换到反应形式。我发现它们比模板驱动的表单更直观,并且您拥有更多的控制权。
    • @Nexus 很有趣。谢谢(你的)信息。令人惊讶的是,经过 24 年的网络开发,我仍然几乎每天都在学习新东西 :)
    • @nomadoda 是的。每次值更改时,验证器都会执行。计时器将对. lookupMember() 的调用限制在指定的时间间隔内。通过使用 switchMap,lookupMember 将被取消,如果仍在进行中,则使用新值再次触发。
    【解决方案2】:

    你可以在你的承诺中创建一个 Observable 来完成去抖动。

    这个逻辑可能不会被剪切和粘贴,但应该能让你接近。

    import {distinctUntilChanged, debounceTime, switchMap} from 'rxjs/operators';
    
     validate(c: FormControl) {
      return new Promise(resolve => {
        new Observable(observer => observer.next(c.value)).pipe(
          debounceTime(600),
          distinctUntilChanged(),
          switchMap((value) => { return this.validateEmailService.validateEmail(value) })
        ).subscribe(
          (res) => {
            console.log(res);
            if (res.valid) {
              resolve(null);
            } else {
              resolve({
                valid: {
                  valid: false
                }
              });
            }
          }
        )
      })
    }
    

    【讨论】:

    • 我收到一个错误“找不到名称'tap'”。有什么想法吗?
    • 将此添加到您的组件中。从 'rxjs/operators' 导入 { tap }
    • 我编辑了答案以在代码示例的顶部包含您需要的所有导入。
    • 越来越近了!在点击线上,第二个值 - validateEmail(value) - 给我一个错误:“{}”类型的参数不可分配给参数类型“字符串”
    • 我需要在您的 validateEmailService 中查看您的 validateEmail 函数。我怀疑该函数对作为参数传递给它的格式不满意。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-03-06
    • 1970-01-01
    • 2017-11-30
    • 2019-11-09
    • 1970-01-01
    • 2021-04-25
    • 1970-01-01
    相关资源
    最近更新 更多