angularvalidationangular-reactive-formsangular2-form-validation

Dynamic validation based on conditions


Scenario: I have 4 form fields.

  1. Description (Optional)
  2. Select Type (Required)
  3. Phone (Required only if Select Type is set to 'Phone')
  4. Email (Required only if Select Type is set to 'Email')

When I change anything is Select Type field, based on the selection, Phone field or Email field will be visible. I need to validate these fields.

Problem:

When the form loads, it'll have only description, select type dropdown and save button.

Step 1: Click save button without entering any input, should throw an alert saying Select Type is required and select type will be red.

Step 2: Select a type, the next input becomes visible with red border. This should not happen since the user didn't touched the field. How can I solve this?

Code:

Stackblitz code

html

<div class="example-container">
    <form [formGroup]="groupForm" (ngSubmit)="onSubmit()">
        <section>
            <section class="input-row">
                <mat-form-field>
                    <input matInput type="test" placeholder="Description" id="description" formControlName="description"/>
        </mat-form-field>
            </section>
            <section class="input-row">
                <mat-form-field>
                    <mat-select id="sourceType" formControlName="sourceType" placeholder="Select Type*">
                        <mat-option value="phone">Phone</mat-option>
                        <mat-option value="email">Email</mat-option>
                    </mat-select>
                </mat-form-field>
            </section>
            <section *ngIf="typeIsPhone" class="input-row">
                <mat-form-field>
                    <input matInput type="number" placeholder="Phone" id="phoneValue" formControlName="phoneValue"/>
        </mat-form-field>
            </section>
            <section *ngIf="typeIsEmail" class="input-row">
                <mat-form-field>
                    <input matInput type="email" placeholder="Email" id="emailValue" formControlName="emailValue"/>
        </mat-form-field>
            </section>
        </section>
        <button mat-raised-button color="primary" type="submit" class="save">
      Save
    </button>
    </form>
</div> 

component:

export class FormFieldOverviewExample implements OnInit {
  typeIsPhone = false;
  typeIsEmail = false;
  public groupForm: FormGroup = new FormGroup({
    description: new FormControl(""),
    sourceType: new FormControl("", [Validators.required]),
    phoneValue: new FormControl("", [Validators.required]),
    emailValue: new FormControl("", [Validators.required])
  });

  constructor() {}

  ngOnInit(): void {
    this.groupForm
      .get("sourceType")
      .valueChanges.subscribe(this.setSourceType.bind(this));
  }

  setSourceType(SourceType: string) {
    this.typeIsPhone = SourceType === "phone";
    this.typeIsEmail = SourceType === "email";
  }

  onSubmit() {
    const sourceTypeFormControl = this.groupForm.get("sourceType");
    const phoneEnteredFormControl = this.groupForm.get("phoneValue");
    const emailEnteredFormControl = this.groupForm.get("emailValue");

    if (sourceTypeFormControl.errors.required) {
      alert("Source Type is required!");
      return;
    } else {
      if (phoneEnteredFormControl.errors.required) {
        alert("Phone is required!");
        return;
      }
      if (emailEnteredFormControl.errors.required) {
        alert("email is required!");
        return;
      }
    }
  }
}

Solution

  • As if a FormControl is disabled don't has errors I suggest another aproach using disable and enable that you can see in this stackblitz

    ngOnInit(): void {
        const control=this.groupForm.get("sourceType")
        if (control)
           control.valueChanges.pipe( //Use startWith to execute at first
                 startWith(control.value)
           ).subscribe(res=>this.setSourceType(res)); //<--see how pass the value
      }
    
      setSourceType(SourceType: string) {
        this.typeIsPhone = SourceType === "phone";
        this.typeIsEmail = SourceType === "email";
        const phoneControl=this.groupForm.get('phoneValue')
        const emailControl=this.groupForm.get('emailValue')
        if (phoneControl) 
          phoneControl[SourceType==='phone'?'enable':'disable']() //(*)
        if (emailControl)
          emailControl[SourceType==='email'?'enable':'disable']()
      }
      //(*) is a abreviated way to say 
      //             if (SourceType=='phone')
      //                phoneControl.enable()
      //             else
      //                phoneControl.disable()
    

    NOTE:

      //You can not use 
      if (phoneEnteredFormControl.errors.required) //WRONG
      //use 
      if (phoneEnteredFormControl.errors && phoneEnteredFormControl.errors.required) //OK