是否可以在 Angular 反应式表单中验证表单外的条件?

Is it possible in Angular reactive forms to validate on conditions out of the form?

我正在研究 Angular 反应式表单验证。我有一个带有 google 自动完成的输入:

<input autocorrect="off" autocapitalize="off" spellcheck="off" type="text" class="input-auto input" formControlName="address">

这是标准实现,每次输入一些关键字时,您都会得到地点建议:

现在,我想做的是验证地址。 地址需要包含邮政编码 - 只有这样才有效。因此,一旦您键入内容并选择其中一项建议,就应该触发验证。 When chosen suggestion contains zip code is valid, when not - is invalid.

如您所见,整个地址只有一个表单控件(应该保持这样),我用 google API 中的格式化地址填充它。我还从 google 处 API 获取有关地址组件的信息,我将其存储在全局变量中(zipCodecountryNamecityNamestreetName):

    this.mapsAPILoader.load().then(() => {
      const autocomplete = new window['google'].maps.places.Autocomplete(this.searchElementToRef.nativeElement, {
        types: ['address']
      });
      autocomplete.addListener('place_changed', () => {
        this.ngZone.run(() => {

          const place = autocomplete.getPlace();

          if (place.geometry === undefined || place.geometry === null) {
            return;
          }

          this.form.get('address').setValue(place.formatted_address);

          for (const addressComponent of place.address_components) {
            for (const addressComponentType of addressComponent.types) {
              switch (addressComponentType) {
                case 'postal_code':
                  this.zipCode = addressComponent.long_name;
                  break;
                case 'country':
                  this.countryName = addressComponent.long_name;
                  break;
                case 'locality':
                  this.cityName = addressComponent.long_name;
                  break;
                case 'route':
                  this.streetName = addressComponent.long_name;
                  break;
              }
            }
          }
        });
      });
    });

在使用 FormBuilder 创建表单的方法中,我使用自定义验证器:

  public createFormGroup(): FormGroup {
    return this.fb.group({
      address: [null, this.zipCodeValidator()]
    });
  }

使用以下自定义验证方法,我想在地址中缺少 zipCode 时收到错误消息:

  public zipCodeValidator(): ValidatorFn {
    return (control: AbstractControl): Observable<{ [key: string]: boolean } | null> => {
      if (this.zipCode !== undefined) {
        return of({ zipCode: true });
      }
      return null;
    };
  }

但是表格没有被正确验证,因为无论我得到的地址有没有邮政编码——它总是有效的:

如果我将相关值用作条件表单控件,则验证工作正常。因此,当没有输入任何内容时,将验证方法更新为以下状态会产生错误:

  public zipCodeValidator(): ValidatorFn {
    return (control: AbstractControl): Observable<{ [key: string]: boolean } | null> => {
      if (control.value === null) {
        return of({ noValue: true });
      }
      return null;
    };
  }

问题:
是否可以像这样验证表单 - 条件实际上不在表单中(因为我没有将此 zipCode 值直接传递给任何表单控件)?

答案是,可以在不符合形式的条件下进行验证,而且通常它非常接近我。

首先我搞砸了验证方法。 Observable 我用的是 async-validation。所以在修复它之后,该方法将 return 只有对象,而不是 Observable:

  public zipCodeValidator(): ValidatorFn {
    return (control: AbstractControl): { [key: string]: boolean } | null => {
      if (control.value !== null) {
        // if value exists, we can add number of conditions
        if (this.zipCode !== undefined) {
          return { zipCode: true };
        }
      } else {
          // here we can react when there is no value entered - act as validator.required
          return { empty: true}
        }
      return null;
    };
  }

然后我开始完成验证,但针对的是 zipCode 变量的先前状态。我想因为也许我需要使用异步验证,但它更简单。我过早地设置了地址表单控件的值:this.form.get('address').setValue(place.formatted_address);
所以我把它移到我正在寻找邮政编码的部分后面并且它起作用了:

this.mapsAPILoader.load().then(() => {
  const autocomplete = new window['google'].maps.places.Autocomplete(this.searchElementToRef.nativeElement, {
    types: ['address']
  });
  autocomplete.addListener('place_changed', () => {
    this.ngZone.run(() => {

      const place = autocomplete.getPlace();

      if (place.geometry === undefined || place.geometry === null) {
        return;
      }

      for (const addressComponent of place.address_components) {
        for (const addressComponentType of addressComponent.types) {
          switch (addressComponentType) {
            case 'postal_code':
              this.zipCode = addressComponent.long_name;
              break;
            case 'country':
              this.countryName = addressComponent.long_name;
              break;
            case 'locality':
              this.cityName = addressComponent.long_name;
              break;
            case 'route':
              this.streetName = addressComponent.long_name;
              break;
          }
        }
      }

      this.form.get('address').setValue(place.formatted_address);
    });
  });
});

还有一个问题是如果我想在其他时刻而不是从一开始就触发验证怎么办。然后只需要将它设置在正确的位置并更新验证器:

this.form.get('address').setValidators(this.zipCodeValidator());
this.form.get('address').updateValueAndValidity();

此外,如果在某些时候需要删除它,这里是简单的方法:

this.form.get('address').clearValidators();