6

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

Community
  • 1
  • 1
Tudor Merlas
  • 287
  • 1
  • 6
  • 16

2 Answers2

4

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(''));
Kurt Hamilton
  • 12,490
  • 1
  • 24
  • 40
3

it's logic, the control touched is form.get('subForm'). And sorry I can not find a good solution about this. I never understand why create a custom form control when the more easy is use a component, see, e.g. this SO or this post from Tomas Trajan

I suggest this aproach, else you need make a method in each custom form control to markAll as touched and called from parent

Eliseo
  • 50,109
  • 4
  • 29
  • 67
  • Eventually, this is exactly what I ended up doing. I declared an interface which has a markFormAsTouched function that does what it says. I made each of my subforms implement the interface and then called the function using ViewChild. – Tudor Merlas Sep 18 '20 at 13:12
  • Hi @TudorMerlas, do you have any code example from waht you explained about adding interface ? – Geoffrey Lalloué Jul 01 '21 at 16:11