1

I've been looking through the internet and couldn't find any answer.

I'm using angular 10 and reactive forms.

Here is my problem: Clicking on submit button triggers validity on my form on the button level but not at a child component level

Here is the StackBlitz

if you press "save" in this example you'll see only the first input triggers validation and goes red whereas the other needs to be manually clicked on. I'm having a formGroup with 2 controls, 1 is a FormControl and the other one a FormArray. I pass the main formGroup to the child component and push 1 formGroup inside the formArray, this formGroup has one formControl. So basically I have at the leaf two FormControl. please tell me if that don't make sense.

So instead I would like validity for all of the elements to be checked, weither it is inside a child component or not.

JSmith
  • 4,519
  • 4
  • 29
  • 45

3 Answers3

4

Angular doesn't have a mechanism to auto-validate all form controls and sub-form controls, that task is left for the developer:

  save() {
    this.validateAllFormFields(this.fg);
  }
  ngOnInit() {
    this.fg = new FormGroup({
      name: new FormControl(
        { value: "", disabled: false },
        Validators.required
      ),
      sub: new FormArray([])
    });
  }

  validateAllFormFields(formGroup: FormGroup | FormArray) {
    Object.keys(formGroup.controls).forEach(field => {
      const control = formGroup.get(field);
      if (control instanceof FormControl) {
        control.markAsTouched({ onlySelf: true });
      } else if (control instanceof FormGroup || control instanceof FormArray) {
        this.validateAllFormFields(control);
      }
    });
  }
Rachid O
  • 13,013
  • 15
  • 66
  • 92
  • Thanks for your answer, What do you think about the `controlValueAccessor` method of @transformer? – JSmith Jan 24 '21 at 21:31
  • the custom controlValueAccessor approach would work if the sub-form has direct FormControls (i.e no deeply nested controls), the solution I provided is for a general purpose usecase – Rachid O Jan 24 '21 at 22:32
  • ok @karavanjo seemed the best solution, even though I'm not understanding everything. Thanks again for your help – JSmith Jan 24 '21 at 22:42
  • Can't you just call formGroup.markAllAsTouched() instead of looping over every FormControl? – JensW Feb 25 '22 at 12:52
2

You can use ControlContainer in viewProviders like this:

control-container.ts

import { Provider, SkipSelf } from '@angular/core';
import { ControlContainer } from '@angular/forms';

export function controlProviderFactory(container: ControlContainer) {
  return container;
}

export const CONTROL_CONTAINER: Provider = {
  provide: ControlContainer,
  useFactory: controlProviderFactory,
  deps: [[new SkipSelf(), ControlContainer]],
};

child.component.ts

import { Component, OnInit, Input } from "@angular/core";
import { FormArray, FormControl, FormGroup, Validators } from "@angular/forms";
import { CONTROL_CONTAINER } from "../control-container";

@Component({
  selector: "app-child",
  templateUrl: "./child.component.html",
  styleUrls: ["./child.component.css"],
  viewProviders: [CONTROL_CONTAINER]
})
export class ChildComponent implements OnInit {
  @Input() public form: FormGroup;

  constructor() {}
  ngOnInit() {}
}

child.component.html

<ng-container formArrayName="subForm">
    <mat-input-container [formGroupName]="0">
    <mat-form-field>
          <input matInput formControlName="subControl">
    </mat-form-field>
    </mat-input-container>
</ng-container>

And initialize in constructor of AppComponent:

  constructor (private formBuilder: FormBuilder) {
    this.fg = this.formBuilder.group({
      name: [null, Validators.required],
      subForm: this.formBuilder.array([])
    });

    const control = <FormArray>this.fg.controls['subForm'];

    control.push(this.formBuilder.group({
      subControl: ['', Validators.required]
    }));
  }

There is an error "mat-error does not display on submit when fields are added in a FormArray". It's described on GitHub here.

Working example based on your code you can find here.

karavanjo
  • 1,626
  • 4
  • 18
  • 31
  • Thanks this is great! can you add a bit more of an explanation reagarding the control container object. I'll implement that and mark as answered if this works – JSmith Jan 24 '21 at 21:44
  • I've made few changes in order to initialise the formArray in the child component and it worked. Great!!! – JSmith Jan 24 '21 at 22:03
0

For a sue case, lets say you have dashboard with child widgets scenario

  1. This NGX-Sub-Form works well as a turnkey, I like this approach because it breaks up the complexity as a second/level sub form simply by extending one the 4 classes/options

  1. Angular recommended approach of parent child interaction

Below example borrowed from penley chan

@Directive({
selector: '[provide-parent-form]',
providers: [
    {
        provide: ControlContainer,
        useFactory: function (form: NgForm) {
            return form;
        },
        deps: [NgForm]
    }
  ]
})
export class ProvideParentForm {}

Usage: In your component at the root element before you have [(ngModel)] add the directive. Example:

<div provide-parent-form> 
   <input name="myInput" [(ngModel)]="myInput"> 
</div>

Now if you output your form object in your console or whatever you can see your component's controls under controls property of your form's object.

Transformer
  • 6,963
  • 2
  • 26
  • 52
  • thank you for this answer but I've already wen't through this example, your example uses `ngForm` I use reactive form. any idea? – JSmith Jan 24 '21 at 19:58
  • ahh i see, I have something will post tomorrow, but in the meantime here is an example. https://indepth.dev/posts/1245/angular-nested-reactive-forms-using-controlvalueaccessors-cvas there the icontrolAccessorValidator... don't exactly recall thats the key – Transformer Jan 24 '21 at 20:05
  • thanks you I'll check that and be waiting for your answser – JSmith Jan 24 '21 at 20:09