如何在 angular 中创建一个自定义验证器来检查基于另一个表单域的表单域?

how to make a custom validator in angular to check a form field based on the other form field?

组件的打字稿是:

export class EtcAddAuthorityComponent implements OnInit {
   addAuthorityForm: FormGroup;
   authTypes: any[] = [];
   loading = false;
   numericRegex = /^[0-9]*$/;

   alphabeticRegex = /^[a-zA-Z]*$/;

   constructor(
      private readonly dialogRef: MatDialogRef<EtcAddAuthorityComponent>,
      private readonly fb: FormBuilder,
      private readonly toastr: ToastrService,
      private readonly ecmService: EcmService,
      private readonly ecmToolChangeService: EcmToolChangeService,
      private readonly cgpAlertDialogService: CgpAlertDialogService,
      @Inject(MAT_DIALOG_DATA) public readonly selectedTool: any
   ) {
      this.addAuthorityForm = this.fb.group({
         authNumber: ['', [Validators.maxLength(20), Validators.pattern(this.alphabeticRegex)]],
         authNumberN: ['', [Validators.required, Validators.maxLength(20), Validators.pattern(this.numericRegex)]],
         authTypeName: ['', [Validators.required, Validators.maxLength(10)]],
         authDescription: ['', [Validators.maxLength(500)]]
      });
   }



   ngOnInit() {
      this.loading = true;
      this.ecmService.getAuthTypeCriteria()
         .subscribe({
            next: (res) => {
               if (res) {
                  this.authTypes = res;
               }
            },
            complete: () => this.loading = false
         });
   }

   onCancelClick() {
      this.dialogRef.close();
   }

   onAddClick() {

      const authNFieldifAuthTypeName = this.authFieldCheck();
      if (authNFieldifAuthTypeName === true) {
         return;
      }
      else {

         const body = {
            ...this.addAuthorityForm.value,
            partChangeId: this.selectedTool.partChangeId,
            toolChangeId: this.selectedTool.toolChangeId
         };

         this.loading = true;
         this.ecmToolChangeService.addAuthority(body)
            .subscribe({
               next: (res) => {
                  this.cgpAlertDialogService.showAlertDialog({
                     title: 'Add Authority',
                     message: 'Authority was added successfully!',
                     alert: cgpAlertTypes.success,
                     closeLabel: 'OK'
                  }).afterClosed().subscribe(() => this.dialogRef.close({ reload: true }));
               },
               error: (err) => {
                  this.loading = false;
                  this.cgpAlertDialogService.showAlertDialog({
                     title: 'Add Authority',
                     message: 'Authority could not be added. Please try again!',
                     alert: cgpAlertTypes.danger,
                     closeLabel: 'OK'
                  });
               },
               complete: () => this.loading = false
            });
      }
   }

   authFieldCheck(): boolean {
      const matched: boolean = (this.addAuthorityForm.controls.authTypeName.value === 'EWO') && (!this.addAuthorityForm.controls.authNumber.value);

      if (matched) {
         this.addAuthorityForm.controls.authTypeName.setErrors({
            notFilled: true
         });
      }
      else {
         this.addAuthorityForm.controls.authTypeName.setErrors({ notMatched: false });
      }
      return matched;
   }


}

html代码是:

<h1 mat-dialog-title>Add Authority</h1>
<div mat-dialog-content>
   <form class="flex-dialog-container" [formGroup]="addAuthorityForm">
      <mat-form-field>
         <mat-label>Authority #(alpha)</mat-label>
         <input matInput autocomplete="off" formControlName="authNumber" #authNumber>
         <mat-error *ngIf="authNumber.value?.length > 20">Cannot exceed 20 characters.</mat-error>
      </mat-form-field>

      <mat-form-field>
         <mat-label>Authority #(numeric)</mat-label>
         <input matInput autocomplete="off" formControlName="authNumberN" #authNumberN>
         <mat-error *ngIf="addAuthorityForm.controls.authNumberN.hasError('required')">Required</mat-error>
         <mat-error *ngIf="authNumberN.value?.length > 20">Cannot exceed 20 characters.</mat-error>   
      </mat-form-field>

      <mat-form-field>
         <mat-label>Authority Type</mat-label>

         <mat-select formControlName="authTypeName">
            <mat-option *ngFor="let at of authTypes" [value]="at.authTypeName">
               {{at.authTypeName}}
            </mat-option>
         </mat-select>
         <mat-error *ngIf="addAuthorityForm.controls.authTypeName.hasError('required')">Required</mat-error>
         <mat-error *ngIf=" this.addAuthorityForm.controls.authTypeName.hasError('notFilled')">Authority #(alpha) required</mat-error>
      </mat-form-field>

      <mat-form-field>
         <mat-label>Authority Description</mat-label>
         <input matInput autocomplete="off" formControlName="authDescription" #authDescription>
         <mat-error *ngIf="authDescription.value?.length > 500">Cannot exceed 500 characters.</mat-error>
      </mat-form-field>

   </form>

</div>
<div mat-dialog-actions class="mat-dialog-actions-end no-margin">
   <button mat-raised-button mat-dialog-close cdkFocusInitial (click)="onCancelClick()"
      (keypress.enter)="onCancelClick()">Cancel</button>
   <button mat-raised-button color="primary" (click)="onAddClick()" (keypress.enter)="onAddClick()" [disabled]="addAuthorityForm.invalid">Add</button>
</div>

这是我的添加对话框: The Add dialog box

如何添加自定义验证,以便在为 'Authority Type' 下拉列表选择 'EWO' 选项时,如果未输入 'Authority# (Alpha)',它将显示错误。但如果为 'Authority Type' 下拉菜单选择 'EWO' 选项,它不应该显示任何错误。

如果您不选择EWO,您可以禁用“权限”,因此Angular不检查是否需要。 disable/enable 你需要使用方法 disable and enable

您可以使用 the control, subscribe to valueChanges,或者在使用 mat-select 时使用事件 selectionChange,例如 (*):

<mat-select formControlName="authTypeName"
    (selectionChange)="addAuthorityForm.get('authDescription')
                 [$event.value=='EWO'?'enable':'disable']()">
    <mat-option *ngFor="let at of authTypes" [value]="at.authTypeName">
       {{at.authTypeName}}
    </mat-option>
 </mat-select>

我做了一个simple stackblitz

(*) 别忘了,在创建表单组时,创建控件启用或禁用

更新 如果我们不想禁用该控件,我们真的需要创建一个自定义表单控件验证。

我们可以对 FormControl、FormGroup 或 FormArray 进行自定义表单控件验证。在这种情况下,我们选择通过 FromControl 创建它。但是我们需要考虑到,如果我们更改 authTypeName,我们需要指示 Angular 检查 authDescription 是否有效

<mat-select formControlName="authTypeName" 
   (selectionChange)="form.get('authDescription').updateValueAndValidity()">
      ...
</mat-select>

好了,我们的自定义表单验证。因为我们有“控制”,在control.parent”我们有“形式”,所以它很容易像

  requiredIf(requiredValue:string){
    return (control:FormControl)=>{
      const form=control.parent;
      if (form)
      {
        //really we need decalre controlDescription, it's the
        //same of "control"
        const controlDescription=form.get('authDescription')
        const controlTypeName=form.get('authTypeName')
        if (controlTypeName && controlDescription && 
            controlTypeName.value==requiredValue && !controlDescription.value)
           return {required:true}
      }
      return null
    }
  }

我们可以写

  form=new FormGroup({
    authDescription:new FormControl(null,this.requiredIf('EWO')),
    authTypeName:new FormControl('EWO')
  })

在声明formGroup

时看到值'EWO'是固定的

new stackblitz

当您说“但是如果为 'Authority Type' 下拉菜单选择了 'EWO' 选项时,它不应该显示任何错误,我不确定您的意思。”。如果没有为该场景输入 'Authority# (Alpha)',我假设它不应该显示任何错误。

可能有更好的解决方案,但这是我在我的项目中使用的方法。您可以在表单初始化之后放置此块,以便表单已经具有这些 auth type 和 auth number 控件可以访问:

this.addAuthorityForm.get('authTypeName').valueChanges.subscribe((newValue) => {
    const authNumber = this.addAuthorityForm.get('authNumber');

    // I don't know the exact structure of the authTypeName so you can debug and change the condition if needed
    if (newValue === 'EWO') { 
        authNumber.setValidators([Validators.required, Validators.maxLength(20), Validators.pattern(this.alphabeticRegex)]);
    } else {
        authNumber.setValidators([Validators.maxLength(20), Validators.pattern(this.alphabeticRegex)]);
    }
    authNumber.updateValueAndValidity();
})

基本上,这样做是在通过添加所需的验证器更改验证类型时重新分配验证器的验证器。我使用 Validators.required 因为它是开箱即用的,但如果你想让它更自定义,你可以使用这样的东西:

...
authNumber.setValidators([(c: FormControl) => {
    return c.value ? null : {required: {valid: false}};
}, Validators.maxLength(20), Validators.pattern(this.alphabeticRegex)]);
...

updateValueAndValidity方法是在用户切换授权类型时重新验证字段。