Angular 11 - Validating a child form generated in a for loop

1.2k Views Asked by At

I am using Angular Reactive forms. My parent component has a form, an Add Form. Submit and Reset buttons. When I click on Add form it adds a child form called profile-form to the DOM. The profile-form has two fields First Name and Email. The idea is to reuse the profile-form component for any number of times, the add button is clicked. Each form needs to be validated and the parent form should know the validation status of each child form.

My parent form has this HTML to generate the child forms in a for loop.

<div *ngFor="let fg of formList.controls; let infoIndex = index">
    <app-profile-form formControlName="fg" [formList]="formList" 
                      [formIndex]="infoIndex"></app-profile-form>
  </div>

When I look at the console, I seem to be getting an error that formControlName 'fg' is not found in formList.controls. How do I fix this mapping of formControls between my parent and child form, such that the validation works?

Stackblitz here

1

There are 1 best solutions below

0
On BEST ANSWER

Lets try to analyze the form.

You expect below as the end value of the form

{
  "formList": [  
    {
      "firstName": "",
      "email": ""
    },
    {
      "firstName": "",
      "email": ""
    },
    {
      "firstName": "",
      "email": ""
    }
  ]
}

In the above we have

form  => FormGroup : form 
  formList => FormArray : formList 
    1 => FormControl with value {email: '', firstName} : 1
    2 => FormControl with value {email: '', firstName} : 2
    3 => FormControl with value {email: '', firstName} : 3

So in the form we have to have this structure for the form to work

<form [formGroup]="signupForm" (ngSubmit)="submit()">
  <ng-container formArrayName='formList'>
    <div *ngFor="let fg of formList.controls; let infoIndex = index">
      <app-profile-form [formControlName]="infoIndex" [formIndex]="infoIndex"></app-profile-form>
    </div>
  </ng-container>

  <button>Sign Up</button>
  <button type="button" (click)="resetForm()">Reset</button>
</form>

Some other amendments will include

Changing formListGroupDef

    return this.formBuilder.control(
      {
        firstName: "",
        email: ""
      },
      Validators.required
    );

SignUpForm

    this.signupForm = this.formBuilder.group({
      formList: this.formBuilder.array([
        this.formListGroupDef()
      ])
    });

I have also made a few changes in your ProfileFormComponent

export class ProfileFormComponent implements ControlValueAccessor, OnDestroy {
  @Input() formIndex: any;
  destroyed$ = new Subject<any>();
  form: FormGroup;
  get firstNameControl() {
    return this.form.controls.firstName;
  }

  get emailControl() {
    return this.form.controls.email;
  }

  constructor(private formBuilder: FormBuilder) {
    this.form = this.formBuilder.group({
      firstName: ["", Validators.required],
      email: ["", Validators.required]
    });

    this.form.valueChanges.pipe(
      filter(({firstName, email}) => firstName.length > 0 || email.length > 0 ),
      takeUntil(this.destroyed$)
    ).subscribe(value => {
      this.onChange(value);
      this.onTouched();
    });
  }

  ngOnDestroy() {
    this.destroyed$.next();
  }

  onChange: any = () => {};
  onTouched: any = () => {};

  registerOnChange(fn) {
    this.onChange = fn;
  }

  writeValue(value) {
    if (value) {
      this.form.patchValue(value);
    }
  }

  registerOnTouched(fn) {
    this.onTouched = fn;
  }

  validate(_: FormControl) {
    return this.form.valid ? null : { profile: { valid: false } };
  }

See Demo