【问题标题】:Dynamically add/remove validators based on condition根据条件动态添加/删除验证器
【发布时间】:2019-11-24 02:16:33
【问题描述】:

场景:

最初我有一个文本框(Name1)、一个日期选择器(DOB1)和一个复选框(Compare)。 Name1DOB1 都是必需的。单击复选框时,将动态添加另外两个名为 Name2DOB2 的表单控件,并且需要 Name1DOB2 中的任何一个。

所以有效的表格有任何

  1. Name1 DOB1 Name2 或 //如果 Name2 有效,则需要从 DOB2 中删除所需的验证器
  2. Name1 DOB1 DOB2 或 //如果DOB2 有效,则需要从Name2 中删除所需的验证器
  3. Name1DOB1Name2DOB2

在上述所有情况下,表单都是有效的,显示启用提交按钮。

问题:

我曾尝试使用 setValidators,但仍然无法弄清楚我缺少什么。当我单击复选框时,表单仅在所有四个控件都有效时才有效。我只需要其中任何三个有效。

代码:

<form [formGroup]="profileForm" (ngSubmit)="onSubmit()">
  <ion-card class="person1">
    <ion-card-content>
      <ion-list lines="full" class="ion-no-margin ion-no-padding">
        <ion-item>
          <ion-label position="stacked">Name / Number <ion-text color="danger">*</ion-text>
          </ion-label>
          <ion-input type="text" formControlName="NameNumber"></ion-input>
        </ion-item>
        <ion-item>
          <ion-label position="stacked">Date of birth<ion-text color="danger">*</ion-text>
          </ion-label>
          <ion-datetime required placeholder="Select Date" formControlName="DateOfBirth"></ion-datetime>
        </ion-item>
      </ion-list>
    </ion-card-content>
  </ion-card>
  <ion-card class="person2" *ngIf="isComparisonChecked">
    <ion-card-content>
      <ion-list lines="full" class="ion-no-margin ion-no-padding">
        <ion-item>
          <ion-label position="stacked">Name / Number <ion-text color="danger">*</ion-text>
          </ion-label>
          <ion-input type="text" formControlName="NameNumber2"></ion-input>
        </ion-item>
        <ion-item>
          <ion-label position="stacked">Date of birth<ion-text color="danger">*</ion-text>
          </ion-label>
          <ion-datetime required placeholder="Select Date" formControlName="DateOfBirth2"></ion-datetime>
        </ion-item>
      </ion-list>
    </ion-card-content>
  </ion-card>
  <ion-item class="compare-section" lines="none">
    <ion-label>Compare</ion-label>
    <ion-checkbox color="danger" formControlName="IsCompare"></ion-checkbox>
  </ion-item>
  <div class="ion-padding">
    <ion-button color="danger" *ngIf="LicensedStatus" [disabled]="!this.profileForm.valid" expand="block"
      type="submit" class="ion-no-margin">Submit</ion-button>
  </div>
</form>

Ts:

profileForm = new FormGroup({
NameNumber: new FormControl('', [Validators.required, Validators.pattern('^[A-Za-z0-9 _]*[A-Za-z0-9][A-Za-z0-9 _]*$')]),
DateOfBirth: new FormControl('', Validators.required),
IsCompare: new FormControl(false)
});
...
this.profileForm.get('IsCompare').valueChanges.subscribe(checked => {
if (checked) {
    this.profileForm.addControl('NameNumber2', new FormControl('', [Validators.required, Validators.pattern('^[A-Za-z0-9 _]*[A-Za-z0-9][A-Za-z0-9 _]*$')]));
    this.profileForm.addControl('DateOfBirth2', new FormControl('', Validators.required));

    this.profileForm.get('NameNumber2').valueChanges.subscribe(() => {
      if (this.profileForm.get('NameNumber2').valid) {
        this.profileForm.get('DateOfBirth2').clearValidators();
      }
      else {
        this.profileForm.get('DateOfBirth2').setValidators([Validators.required]);
      }
    this.profileForm.get('DateOfBirth2').updateValueAndValidity();
    });

    this.profileForm.get('DateOfBirth2').valueChanges.subscribe(() => {
      if (this.profileForm.get('DateOfBirth2').valid) {
        this.profileForm.get('NameNumber2').clearValidators();
      }
      else {
        this.profileForm.get('NameNumber2').setValidators([Validators.required, Validators.pattern('^[A-Za-z0-9 _]*[A-Za-z0-9][A-Za-z0-9 _]*$')]);
      }
    this.profileForm.get('NameNumber2').updateValueAndValidity();
    });
  }
  else {
    this.profileForm.removeControl('NameNumber2');
    this.profileForm.removeControl('DateOfBirth2');
  }
});

我在这里错过了什么?

更新 #1:

我已经更新了上面的代码。如果我使用updateValueAndValidity,我会在控制台中收到此错误

【问题讨论】:

    标签: angular typescript validation ionic4 angular-forms


    【解决方案1】:

    这是因为updateValueAndValidity() 发出另一个valueChanges 事件。因此,您的订阅会无限地相互触发。

    this.profileForm.get('NameNumber2').valueChanges.subscribe(() => {
      // omitted
      this.profileForm.get('DateOfBirth2').updateValueAndValidity(); // Triggers valueChanges for 'DateOfBirth2' 
    });
    
    this.profileForm.get('DateOfBirth2').valueChanges.subscribe(() => {
      // omitted
      this.profileForm.get('NameNumber2').updateValueAndValidity(); // Triggers valueChanges for 'NameNumber2' 
    });
    

    之前的帖子中已经描述了一种避免这种情况的方法:使用distinctUntilChanged

    虽然方法本身内置了一种更简洁的方法:updateValueAndValidity() 接受一个对象来配置其行为。 updateValueAndValidity({emitEvent: false}) 将阻止valueChanges 事件被发出,从而停止事件循环。

    this.profileForm.get('NameNumber2').valueChanges.subscribe(() => {
      // omitted
      this.profileForm.get('DateOfBirth2').updateValueAndValidity({emitEvent:false}); // Does NOT trigger valueChanges
    });
    
    this.profileForm.get('DateOfBirth2').valueChanges.subscribe(() => {
      // omitted
      this.profileForm.get('NameNumber2').updateValueAndValidity({emitEvent:false}); // Does NOT trigger valueChanges
    });
    

    【讨论】:

      【解决方案2】:

      试试下面的代码。

      this.profileForm.get('DateOfBirth2').setValidators([Validators.required]);
      this.profileForm.get('DateOfBirth2').updateValueAndValidity();
      

      【讨论】:

      【解决方案3】:

      使用 rxjs/operators 中的 distinctUntilChanged 将解决 Maximum call stack size exceeded 错误。

      换行

      this.profileForm.get('NameNumber2').valueChanges.subscribe(() => {
      

      this.profileForm.get('NameNumber2').valueChanges.pipe(distinctUntilChanged()).subscribe(() => {
      

      因此,整体代码将如下所示。

      import { distinctUntilChanged } from 'rxjs/operators';
      
      this.profileForm.get('NameNumber2').valueChanges.pipe(distinctUntilChanged()).subscribe(() => {
           if (this.profileForm.get('NameNumber2').valid) {
              this.profileForm.get('DateOfBirth2').clearValidators();
           }
           else {
             this.profileForm.get('DateOfBirth2').setValidators([Validators.required]);
           }
           this.profileForm.get('DateOfBirth2').updateValueAndValidity();
        });
      
      this.profileForm.get('DateOfBirth2').valueChanges.pipe(distinctUntilChanged()).subscribe(() => {
           if (this.profileForm.get('DateOfBirth2').valid) {
              this.profileForm.get('NameNumber2').clearValidators();
           }
           else {
              this.profileForm.get('NameNumber2').setValidators([Validators.required, Validators.pattern('^[A-Za-z0-9 _]*[A-Za-z0-9][A-Za-z0-9 _]*$')]);
           }
           this.profileForm.get('NameNumber2').updateValueAndValidity();
      });
      

      我运行了上面更改的代码,表单有效,并且为您提到的所有场景启用了提交按钮。

      【讨论】:

      • 谢谢。这行得通,但我更喜欢另一个。但我学到了关于distinctUntilChanged 的新东西:)
      【解决方案4】:

      为什么不在整个表单上使用 customValidator?您发出不同的错误并检查表单上的错误。辅助功能指示您的字段有错误 有些人喜欢:

        form=new FormGroup({
          name1:new FormControl(),
          date1:new FormControl(),
          compare:new FormControl(),
          name2:new FormControl(),
          date2:new FormControl(),
        },this.customValidator())
      
        hasError(error:string)
        {
          return this.form.errors?this.form.errors.error.find(x=>x==error):null
        }
        customValidator()
        {
          return (form:FormGroup)=>{
            const errors=[];
            if (!form.value.compare)
            {
              if (!form.value.name1)
                  errors.push('name1')
              if (!form.value.date1)
                  errors.push('date1')
            }
            else
            {
                ....
            }
            return errors.length?{error:errors}:null
          }
        }
      

      和你的表格一样

      <form [formGroup]="form">
        <input formControlName="name1"/>
        <span *ngIf="hasError('name1')">*</span>
      
        <input formControlName="date1"/>
        <span *ngIf="hasError('date1')">*</span>
        <br/>
        <input type="checkbox" formControlName="compare"/>
        <br/>
        <input *ngIf="form.get('compare').value" formControlName="name2"/>
        <span *ngIf="hasError('name2')">*</span>
        <input *ngIf="form.get('compare').value" formControlName="date2"/>
          <span *ngIf="hasError('date2')">*</span>
      </form>
      

      另一个想法类似,有一个 customValidator 总是返回 null,但使用 setErrors 手动给你的字段一个错误

        customValidator()
        {
          return (form:FormGroup)=>{
            const errors=[];
            if (!form.value.compare)
            {
              if (!form.value.name1)
                  errors.push('name1')
              if (!form.value.date1)
                  errors.push('date1')
            }
            else
            {
               ....other logic...
            }
            form.get('name1').setErrors(errors.find(x=>x=='name1')?{error:"required"}:null)
            form.get('date1').setErrors(errors.find(x=>x=='date1')?{error:"required"}:null)
            form.get('name2').setErrors(errors.find(x=>x=='name2')?{error:"required"}:null)
            form.get('date2').setErrors(errors.find(x=>x=='date2')?{error:"required"}:null)
            return null
          }
        }
      

      【讨论】:

        猜你喜欢
        • 2016-08-01
        • 2018-08-11
        • 2016-11-08
        • 1970-01-01
        • 2021-12-07
        • 2019-11-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多