4
votes

I am trying to build a data-table compatible with FormGroup & FormArray. I will pass header details and rows to this component with parentForm group.

I have created a small add button, on clicking it, I will add new items dynamically. In this case, UI is updating fine, but if I print formGroup.value(), I am getting only the initial value passed. Not the updated formControl. I'm not sure what I am missing.

export class FormTableComponent implements OnChanges {
    @Input() headers: Array<string>;
    @Input() rows: Array<any>;
    @Input() parentFG: FormGroup;
    @Input() entriesName: string;

    get entries(): FormArray {
        return <FormArray>this.parentFG.get(this.entriesName);
    }

    constructor(private fb: FormBuilder, private cd: ChangeDetectorRef) {}

    ngOnChanges() {
        this.createFormControls();
    }

    createFormControls() {
        this.parentFG.controls[this.entriesName] = this.fb.array([]);
        this.rows.forEach(row => {
            this.entries.controls.push(this.fb.group(row));
        });
    }
}

DEMO

2

2 Answers

5
votes

You have a couple of issues: 1. you push new row to controls of FormArray other than FormArray itself

this.entries.controls.push(this.fb.group(row));

should be

this.entries.push(this.fb.group(row));

2. don't create new FormArray everytime and reload every single row when invoke createFormControls(), you can access it by your get entries() method

Solution 1:

Check length of entries, if it is 0, initialize it with rows otherwise just push last row into the existing FormArray

createFormControls() {
        const keys = Object.keys(this.entries.value);
        if( keys.length === 0) {
          this.rows.forEach(row => {
              this.entries.push(this.fb.group(row));
          });
        } else {
          const row = this.rows[this.rows.length - 1];
          this.entries.push(this.fb.group(row));
        }
    }

Solution 2:

Personally, I prefer putting createFormArray() and appendTo() or popFrom() into the parent Component. It is clean and easy read. We don't need ChangeDetectionRef in this case.

I have moved createFormControls() from form-table component to dummy component and rename it to createFormArray() and changed addNewRow() a little bit, now everything works properly

// create FormArray once
createFormArray() {
    const elements: any[] =[];

    this.rows.forEach(row => {
        elements.push(this.fb.group(row));
    });
    return this.fb.array(elements);
}

addNewRow() {
  const newRow =    {
            name: 'NAFFD',
            age: 12,
            place: 'TN'
        };

    this.rows.push(newRow);  // update rows

    const persons =  this.dummyForm.get('persons') as FormArray;
    persons.push(this.fb.group(newRow));  // update FormArray
}

When initializing your dummy form, do below:

   this.dummyForm = this.fb.group({
        persons: this.createFormArray(),
        store: this.fb.group({
            branch: '',
            code: ''
        })
    });

enter image description here

Hope it helps and happy coding!

0
votes

This is a common misconception about ngOnChanges. ngOnChanges works best with Primitive Types, because they are passed by value.

Objects and Arrays in Javascript are passed by reference. That being said, ngOnChanges is looking for the value of the input provided to change. In this case, the reference to your Array is the input provided, not the values within the Array. So unless you are passing a new Array into the component, ngOnChanges will not fire, thus not re-rendering your form.

One quick and dirty solution could be to re-instantiate the Object/Array everytime you update it on the Parent side.

this.rows = Object.assign([], newRows);

This would create an entirely new array, which should in turn change the reference, and finally force ngOnChanges to fire on the child.

Alternatively you could somehow convert your Rows into an Observable and subscribe to it within the child. Personally, Observables are a much cleaner way to deal with Change Detection outside of Primitive Types. Here is a great writeup from the docs on Parent/Child Communication through a Service.