angularvalidationmat-error

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


The typescript for the component is:

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;
   }


}

The html code is:

<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>

This is my add dialog box: The Add dialog box

How to add a custom validation so that if 'EWO' option is chosen for the 'Authority Type' dropdown, it would show an error if the 'Authority# (Alpha)' is not entered. But it should not show any error if the 'EWO' option is chosen for the 'Authority Type' dropdown.


Solution

  • You can disable the "Authority" if you don't choose EWO, so Angular not check if is required or not. To disable/enable you need use the methods disable and enable

    You can use a directive to disable/enable the control, subscribe to valueChanges or, as you're using a mat-select, use the event selectionChange like (*):

    <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>
    

    I make a simple stackblitz

    (*) don't forget, when create the formgroup, create the control enabled or disabled

    Update if we don't want disable the control, really we need create a custom Form control Validation.

    We can make a custom form Control Validation over a FormControl, a FormGroup or a FormArray. In this case we choose make it over a FromControl. But we need take account that, is we change the authTypeName, we need indicate to Angular that check if the authDescription is validate or not

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

    Well, our custom Form validation. As we has the "control", in control.parent" we has the "form", so it's so easy like

      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
        }
      }
    

    And we can write

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

    See that the value 'EWO' is fixed when declare the formGroup

    the new stackblitz