【问题标题】:Async validator is not setting the returned error in the FormControl异步验证器未在 FormControl 中设置返回的错误
【发布时间】:2020-05-01 19:48:51
【问题描述】:

我正在使用 Angular 8 和 Bootstrap 做一个 Web 应用程序。我有一个只有一个输入的表单来输入一个 5 位数字(作为字符串)。我想验证这个数字在数据库中是唯一的。

我试图实现一个异步验证器。它检查数字是否唯一,如果不是,则应返回错误以在模板中显示消息。

brand: Brand;
gambleForm: FormGroup;

constructor(
    private brandService: BrandService,
    private fb: FormBuilder,
  ) {
    this.gambleForm = fb.group({
      number: ['', [ // Default validators
        Validators.required,
        Validators.minLength(5)
      ]]
    });
  }

ngOnInit() {
    // Get the id of the entity coming from the router
    const id = this.route.snapshot.paramMap.get('id');
    this.getBrandData(id);
  }

getBrandData(id: string) {
    this.brandService.getBrandById(id).subscribe(res => {
      this.brand = res;
      this.setNumberAsyncValidator();
    });
  }

// Set async validator
setNumberAsyncValidator() {
    this.gambleForm.get('number').setAsyncValidators([
      CustomValidator.checkNumberDisponibility(
        this.brandService,
        this.brand.id,
        'jsjdlnsdf3jn234'
      )
    ]);
  }

CustomValidators 类:

import {AbstractControl, AsyncValidatorFn} from '@angular/forms';
import {catchError, debounceTime, map, switchMap} from 'rxjs/operators';
import {Observable, of} from 'rxjs';
import {BrandService} from '../core/services/brand.service';

function isEmptyInputValue(value: any): boolean {
  return value === null || value.length === 0;
}

export class CustomValidator {

  static checkNumberDisponibility(brandService: BrandService, brandId: string, raffleId: string): AsyncValidatorFn {
    return (control: AbstractControl):
      Promise<{ [key: string]: any } | null>
      | Observable<{ [key: string]: any } | null> => {
      if (isEmptyInputValue(control.value)) {
        return of(null);
      } else if (control.value.length !== 5) {
        return of(null);
      } else {
        return control.valueChanges.pipe(
          debounceTime(500),
          switchMap(_ =>
            // This method returns: Observable<any[]>
            brandService.getGamblesWithTheNumber(brandId, raffleId, control.value)
              .pipe(
                map(gambles => {
                  // The "exhausted" error is not present in the FormControl
                  return gambles.length ? {exhausted: true} : null;
                }),
                catchError(err => {
                  console.log('Number validator error: ' + err);
                  return of(null);
                })
              )
          )
        );
      }
    };
  }

}

FormControl 中不存在我返回的错误,因此模板中未显示我的自定义消息。换句话说,FormControl 在其“错误”对象中没有任何内容。

我试过没有结果:

setNumberAsyncValidator() {
    this.gambleForm.get('number').setAsyncValidators([
      CustomValidator.checkNumberDisponibility(
        ...
      )
    ]);
    this.gambleForm.get('number').updateValueAndValidity(); // Notice this
  }

但是,这样做确实有效:

map(gambles => {
  return gambles.length ? control.setErrors({exhausted: true}) : null;
}),

另外,这样做也可以:

return control.valueChanges.pipe(
          debounceTime(500),
          switchMap(_ =>
            brandService.getGamblesWithTheNumber(brandId, raffleId, control.value)
              .pipe(
                map(gambles => {
                  console.log('Gambles count: ' + gambles.length);
                  return gambles.length ? {exhausted: true} : null;
                }),
                catchError(err => {
                  console.log('Number validator error: ' + err);
                  return of(null);
                })
              )
          ),
          first(), // Notice this
        );

也许返回的 Observable 没有正确完成,这就是我需要使用 first () 的原因?我在这里很困惑。

我发现的所有异步验证器示例都使用第一种方法,返回{exhausted: true}。出于某种原因,它对我不起作用。我的第二种方法,使用control.setErrors()(我正在测试并进行反复试验并且它有效),我认为这不是最好的方法。甚至 Angular 的文档也没有这样做。

为什么在 FormControl 的错误中没有返回 {exhausted: true}?我错过了什么?

我的目标是正确返回 {exhausted: true} 并将其包含在 FormControl 错误中以在模板中显示我的自定义消息。

【问题讨论】:

  • 你需要在 setNumberAsyncValidator() 函数中返回错误。并添加此函数来验证gambleForm中的数字表单控件。
  • 你必须在你的异步验证器中使用冷可观察。

标签: angular angular-reactive-forms


【解决方案1】:

您使用的是FormBuilder,为什么不在Form中的输入声明中注册Async Validator?

这样

constructor(
    private brandService: BrandService,
    private fb: FormBuilder,
  ) {
    this.gambleForm = fb.group({
      number: ['', 
        [ // Default validators
          Validators.required,
          Validators.minLength(5)
        ],
        [ //Async Validator
         MyAsyncValidator
        ]
      ]
    });
  }

其中 MyAsyncValidator 是异步验证器函数

【讨论】:

  • 我稍后会分配它,因为我需要将一些变量传递给异步验证器,这些变量在表单的构造中不可用。
【解决方案2】:

看起来你做的一切都是正确的,我唯一的猜测是你需要在设置异步验证器后调用updateValueAndValidity()

来自setAsyncValidators方法的文档:

设置在此控件上处于活动状态的异步验证器。调用它会覆盖任何现有的异步验证器。

在运行时添加或删除验证器时,必须调用 updateValueAndValidity() 以使新验证生效。

希望这能解决问题。

更新

我刚刚看到你提到的关于first() 的观点。该运算符将在第一个值之后终止您的订阅。我不确定您的服务调用是如何进行的,但如果您依赖的不仅仅是来自控件的第一个输入,那么这可能就是问题所在。

【讨论】:

  • 我已经尝试过了,但它不起作用。我在设置异步验证器后立即使用了this.gambleForm.get('number').updateValueAndValidity();,完全没有效果。验证器的错误仍然不存在。还有其他想法吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-01-22
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多