【问题标题】:Debounce async Validator去抖动异步验证器
【发布时间】:2019-03-15 02:32:26
【问题描述】:

我有一个有效的异步验证器,它向服务器发出 HTTP 请求以检查用户名是否已被使用。 由于我不想在每次击键后调用 API,我需要对输入流进行去抖动。

我第一次在服务中有throttleTime,但是关于 SO 的另一个主题说这必须是订阅的人,但还没有运气!

我的组件:

this.form = this._fb.group(
      {
        username: ['', [Validators.required, Validators.maxLength(50), NoWhitespaceValidator], [IsUserIdFreeValidator.createValidator(this._managementService)]]
      });

我的验证者:

export class IsUserIdFreeValidator {
  static createValidator(_managementService: ManagementService) {
    return (control: AbstractControl) => {
      return _managementService.isUserIdFree(control.value)
        .pipe(
          throttleTime(5000),
          (map(
            (result: boolean) => result === false ? { isUserIdFree: true } : null))
        );
    };
  }
}

我的服务:

  public isUserIdFree(userId: string): Observable<{} | boolean | HttpError> {
    const updateUserCheck: UpdateUserCheck = new UpdateUserCheck();
    updateUserCheck.userID = userId;

    return this._httpClient.post<boolean>('UserManagementUser/IsUserIdFree', updateUserCheck));
  }

【问题讨论】:

标签: angular typescript rxjs angular-validation


【解决方案1】:

这应该可以解决问题:

  static createValidator(_managementService: ManagementService) {
    const subject = new BehaviorSubject('');
    const observable = subject.asObservable().pipe(
        debounceTime(1000),
        switchMap(val => _managementService.isUserIdFree(val)),
        map((isUserIdFree: boolean) => isUserIdFree ?  null : { userIdTaken : true }),
        ); 
    return (control: AbstractControl) => {
      subject.next(control.value);
      return observable.pipe(takeUntil(timer(5000))); // acts as a way to make observable finite
    }
  }

去抖动应该发生在控件发出的值上,而不是从 http 服务返回的结果上。我们首先在可观察流上发出值并将其通过distinctUntilChanged 进行管道传输,这确保只有与最后发出的值相比不同的值才能通过管道的该阶段。 debounceTime(x) 确保仅发出“x”毫秒后的最后一个值。

switchMap 操作符获取控制值并向后端发出 get 请求,并将新的 observable 传递到管道的下一个阶段。最后,我将您现有的地图运算符应用于后端的结果以生成适当的错误消息。

【讨论】:

  • 如果我的理解是正确的,createValidator 方法将在每次击键时进行评估,这将导致每次都创建单个有限流。这不会产生预期的结果
  • createValidator 是工厂方法,它在闭包中捕获服务并使用它创建函数。每次运行验证时都会调用此函数,所以是的,这不起作用,setTimeout 有一个简单的方法,但我会用 observables 做一些事情
  • @Jota.Toledo 这个怎么样,它是同一个流在多个调用中通过同一个管道,所以应该很好。它还保留请求之间的状态。
  • 不,这仍然不是有限的
【解决方案2】:

我能够通过另一种方式解决这个问题,通过控件上名为“updateOn”的属性。

使用模板驱动的表单:

<input [(ngModel)]="name" [ngModelOptions]="{updateOn: 'blur'}">

使用反应形式:

new FormControl('', {updateOn: 'blur'});

如果你使用表单生成器,从 Angular 7 开始你可以使用this:

this.form = this._fb.group({
  email: ['', [CustomValidators.email], null, 'blur'],
});

【讨论】:

    【解决方案3】:

    使用 observables,我做到了:

    form = this._fb.group({
    email: [null, [Validators.required, Validators.email], [this.validateEmailNotTaken.bind(this)]]
    })
    
    private _emailCancel$ = new Subject<void>();
     validateEmailNotTaken(control: AbstractControl): Observable<object> {
            this._emailCancel$.next();
            return race(
                timer(3000).pipe(map(_ => true)),
                this._emailCancel$.pipe(
                    map(_ => false)
                )
            ).pipe(
                take(1),
                filter(val => !!val),
                switchMap(() => this.isEmailTaken(control.value).pipe(
                    map(res => res ? {isTaken: true } : null)
                ))
            );
        }
    

    【讨论】:

      【解决方案4】:

      对于任何找到这个问题和一组答案的人,恕我直言,这个问题与this one 重复。我认为这个answer 是正确的,除非你喜欢 Promises。我打算用更新的(Angular 9+)解决方案在那里发布答案。

      编辑:here's my suggestion 了解如何执行此操作。

      顺便说一句,在遇到这个问题和另一个问题之前,我尝试了这里列出的所有三个答案。你可以让它们全部工作,但我链接到的答案是最优雅的。很难弄清楚,因为它依赖于隐藏在真正应该在文档中的 Angular 框架中的行为。

      【讨论】:

        猜你喜欢
        • 2019-03-23
        • 1970-01-01
        • 2020-02-22
        • 2023-03-12
        • 2018-08-17
        • 1970-01-01
        • 2017-10-26
        • 2016-08-23
        相关资源
        最近更新 更多