Search code examples
angularangular-forms

Both markAllAsTouched and markAsTouched don't affect subforms


Background information

I have a VERY large form (potentially hundreds of fields) that I decided to break into smaller sub-form components (for obvious reasons). I achieved this using Control Value Accessors. This worked like a charm until it came time to show errors on fields at submit.

The sub-forms are structured like this:

<form [formGroup]="form">
  <app-subform [documentId]="documentId" formControlName="sub-form"></app-subform>

  <!-- Other form fields here ... -->
</form>

To ease my work I created an error component that takes as input a control like in the code below. The code below is part of the sub-form.

<div class="question">
  <label for="tableNumber">
    Figure/table number
    <app-required [type]="'conditional'"></app-required>
  </label>
  <input type="text" id="tableNumber" formControlName="figure"/>
  <app-error [control]="form.get('figure')"></app-error>
</div>

The inside of my error HTML looks something like this:

<span class="errorWrapper" *ngIf="control.invalid && control.touched" (click)="clearError()" [innerHtml]="error"></span>

Example code

While I can't quite share my full code, here's a reproduction of the problem: https://stackblitz.com/edit/encapsulated-sub-forms

The Problem

When I submit my code, in order to get error messages displaying across every input, I mark all inputs as touched using this.form.markAllAsTouched();. While this works like a charm for the inputs that are not part of sub-form components, the sub-form components themselves don't react at at all, remaining untouched.

Solutions I considered

  • My first reaction was - why not mark each control individually as touched? Doesn't work
  • How about I implement an input for each sub-form component that triggers mark as touched? Didn't try, seems more like a hack than a best-practice solution
  • Change my implementation to use ControlContainer. Seems the most promising

What I tried

I tried changing the implementation to use ControlContainer but that comes with its own set of challenges (described in a separate stack overflow question).

My question

So basically, what I want to know is, how can I get my sub-form inputs to show errors on submit, following best practices and with a minimal amount of rewrite (my priorities being in that order)?


Solution

  • The problem here is that your sub-form component is declaring a brand new FormGroup instance. It knows nothing of the FormGroup instance in the parent control.

    My approach here would be to pass nested form groups within the root FormGroup from the parent through to the sub-forms.

    This would allow your sub-forms to update the form group in the parent without the need for two-way binding. And the events triggered on the form group would propagate through the sub forms.

    Example code

    parent.component.ts

    this.form = this.formBuilder.group({
      subGroup1: this.formBuilder.group({}),
      subGroup1: this.formBuilder.group({})
    });
    

    parent.component.html

    <form [formGroup]="form">
      <app-sub-form-1 [form]="form.get('subGroup1')"></app-sub-form-1>
      <app-sub-form-2 [form]="form.get('subGroup2')"></app-sub-form-1>
    </form>
    

    sub-form1.component.ts

    this.form.addControl('sub-group1-control1', new FormControl(''));