2

I am using Angular with reactive forms and material UI. I have a form in which I have a text field that is only required if another field has a certain value - for example, a text input that only appears and is required if a radio button is selected. Here is an example code snippet:

<div>
    Do you have any concerns?
</div>
<mat-radio-group formControlName="haveConcerns">
    <mat-radio-button [value]=true>yes</mat-radio-button>
    <mat-radio-button [value]=false>no</mat-radio-button>
</mat-radio-group>
<div [hidden]="!form.value.haveConcerns">
    <div>
        if "YES", please list the reasons:`enter code here`
    </div>
    <mat-form-field>
        <input formControlName="concerns" matInput placeholder="reasons" type="text">
        <mat-error *ngIf="hasError(this.form, 'concerns', 'maybeRequired')">
            please explain any concerns your may have
        </mat-error>
    </mat-form-field>
</div>

My issue is that the validation is called when the form is first drawn, and it passes because the button is not checked. But when the user clicks the radio button, the field will not re-run its validation since it was not changed.

Right now I am manually setting the value of the concerns field to itself in order to make the validation re-run but it feels like a hack.

public applyForm(formValue) {
    this.formIsSubmitted = true;
    const concernsField= this.form.get('concerns');
    concernsField.setValue(concernsField.value);
    if (this.form.valid) {
        this.submitFormValue(formValue);
    }
}

Is there a better way? If I have a large form, this gets unwieldy. I saw that there is a setDirty method but I tried it on both the form and the control and it didn't appear to do anything.

Dave Vronay
  • 625
  • 7
  • 22
  • 1
    Sounding like a job for [markForCheck](https://angular.io/api/core/ChangeDetectorRef#markforcheck) in the the changedetectorref but would need more of an example to see what's going on like for instance if you've got onPush detection strategy and are detaching anywhere etc. – Chris W. Sep 30 '19 at 19:27

1 Answers1

4

If you have a large form, I would make a nested formgroup for those controls that are dependent on eachother, and then attach the custom validator on that nested form. For example:

this.form = this.fb.group({
  nested: this.fb.group(
    {
      haveConcerns: [false],
      concerns: [""]
    },
    { validators: concernValidator }
  )
});

which is then fired whenever either of the formcontrol value changes.

DEMO HERE

You could of course attach the validator on the main form as well, but if you have a really large form, it would fire unnecessarily when non-relevant fields are edited.

If you do this, you need to though make a custom error state matcher, as mat-error is not shown if a formgroup has an error, only when a formcontrol has an error. How to make a custom error state matcher for this purpose, can be found here.


If you really want to just have the validator on the concerns form control, as you noticed, you must somehow trigger validation on that formcontrol whenever haveConcerns changes. You could achieve this by listening to valueChanges (or just a function that listens to the change) for that form control and call this.form.get('concerns).updateValueAndValidity(), which would trigger the custom validator for that field, like:

this.form.get("haveConcerns").valueChanges.subscribe(data => {
  this.form.get('concerns').updateValueAndValidity();
});

If you do this, remember to unsubscribe on component OnDestroy

AT82
  • 71,416
  • 24
  • 140
  • 167