Anthony D
Anthony D

Reputation: 372

Angular dynamic formArray can't find the controls

I have troubles building a dynamic form with the help of the FormArray, i'm trying to build a form based on an array that contains Object describing a row of a database. The goal is to show the user every row that he selected with their current values letting the user modify them and send them back.

So everything need to be modular because i don't know the number of rows that he selected neither the number of columns of the table, and i still need to be able to retreive the new data.

I managed to do something that should work but i still have an error :

Error : Cannot find control with name: 'XXXXX'

I feel like it might be a story of lifeCycleHook and the html is somehow executing before the controls are set up in the typescript.

I first created a formArray named modifyForm and a formBuilder

 modifyForm: FormArray;

 constructor private fb: FormBuilder){}

and then in my ngOnInit function i created a loop that fill the modifyForm with the right controls and also fill the externalArray that the HTML will use to create the user interface.

Typescript :

ngOnInit() {


    let internalObjects = {}

    for (let i = 0; i < this.selectedRows.length; i++) {
      const row = this.selectedRows[i];
      internalObjects = {}

      for (let y = 0; y < Object.keys(row).length; y++) {
        const key = Object.keys(row)[y];
        const value = Object.values(row)[y];

        if (key !== "#") {
          this.modifyForm.push(this.fb.control(i + key))
          internalObjects[key] = value;
        }
      }
      this.externalArray.push(internalObjects)
    }
  }
}

HTML :

<form [formGroup]="modifyForm">
        <div *ngFor="let object of externalArray; let item = index ">
                <div *ngFor="let value of object | keyvalue">
                    <mat-form-field>
                       <input matInput [formControlName]='item+value.key' [value]='value.value'>
                    </mat-form-field>
               </div>
         </div>
</form>

What's weird is that at the end of the ngOnInit function i console Log the controls it seems to created with the proper values and it still say that it can't find them.

Typescript loops :

   for (const control of this.modifyForm.controls) {
      console.log(control.value)
    }

The result :

1

I know it's a lot to digest, i tried to give as many informations as i could and i hope it's not too hard to read. I've been searching for days and i'm still stuck here. Thanks to anyone who could help ! :)

Upvotes: 0

Views: 4217

Answers (1)

Kurt Hamilton
Kurt Hamilton

Reputation: 13515

You're not binding to your form array correctly. With form groups and form arrays you bind to the key. In the case of form arrays, this is the index.

this.modifyForm.push(this.fb.control(i + key))

This is adding a form control with the value i + key at the next available index in the form array.

In your HTML you should bind to the index instead of the value. You shouldn't need to manually set the [value] - that comes from the form binding.

<input matInput [formControlName]='item' />

Where item is the current index in the form array as defined in *ngFor

Binding to complex objects

If you want to create an array of multiple form controls, you would set up an array of form groups and your HTML directives would reflect your form structure.

const formGroups: FormGroup[] = myArray.map(x => new FormGroup({
  prop1: new FormControl(x.prop1),
  prop2: new FormControl(x.prop2)
}));
this.form = new FormArray(formGroups);
<form [formGroup]="form">
  <div *ngFor="let item of myArray; let i = index"
      [formGroupName]="i"> <!-- <<< bind to the ith form array entry -->
    <input formControlName="prop1" />
    <input formControlName="prop2" />
  </div>
</form>

Binding to unknown properties

If you don't know what your object properties are, you can create your form from your object keys.

this.form = new FormArray(this.selectedRows.map(row => {
  const formGroup = new FormGroup({});
  Object.keys(row).forEach(key => { // <-- use Object.keys to iterate the object
    formGroup.addControl(key, new FormControl(row[key]));
  });
  return formGroup;
}));

And generate your HTML from the object keys

<form [formGroup]="form">
  <div *ngFor="let row of selectedRows;let i = index">                
    <div [formGroupName]="i">
      <!-- use the keyvalue pipe to iterate the key/value pairs of the object -->
      <span *ngFor="let keyvalue of row | keyvalue">
        <input [formControlName]="keyvalue.key" />
      </span>
    </div>
  </div>
</form>

DEMO: https://stackblitz.com/edit/angular-v1jd6x

Upvotes: 1

Related Questions