Angular FormArray patchValue error: TypeError: value.forEach is not a function - how to resolve?

lynnjwalker picture lynnjwalker · Sep 29, 2017 · Viewed 14.2k times · Source

Page was originally written as a template driven form, but is being converted to reactive in order to add auto-save feature.

In the page component's constructor I define the reactive form variable:

 this.form = fb.group({
    reviewer: '',
    position: '',
    officeName: '',
    rows: fb.array([]),
    comments1: '',
    comments2: '',
    comments3: ''
});

'rows' is the field relevant to this issue.

In the ngInit a request is made, through a web api, to retrieve persisted form data. The data is retrieved as an array of rows, each containing an array of 6 cells. The third cell will be tied to a FormControl, the others will be rendered as static text.

this.rows = summaryReportQuestion.map(summaryReportQuestionObj => {
    return {
        cells: [
            summaryReportQuestionObj.questionID,
            summaryReportQuestionObj.question,
            (summaryReportQuestionObj.columnSign == '$' ? (summaryReportQuestionObj.columnSign + ' ' + summaryReportQuestionObj.target) : summaryReportQuestionObj.target + ' ' + summaryReportQuestionObj.columnSign),
            (summaryReportQuestionObj.budget == null ? summaryReportQuestionObj.budget : (summaryReportQuestionObj.columnSign == '$' ? summaryReportQuestionObj.columnSign + ' ' + this.utilityService.formatNumberWithCommas(Math.floor(summaryReportQuestionObj.budget), false) : summaryReportQuestionObj.budget + ' ' + summaryReportQuestionObj.columnSign)),
            (summaryReportQuestionObj.average == null ? summaryReportQuestionObj.average : (summaryReportQuestionObj.columnSign == '$' ? summaryReportQuestionObj.columnSign + ' ' + this.utilityService.formatNumberWithCommas(Math.floor(summaryReportQuestionObj.average), false) : summaryReportQuestionObj.average + ' ' + summaryReportQuestionObj.columnSign)),
            (summaryReportQuestionObj.top20Percent == null ? summaryReportQuestionObj.top20Percent : (summaryReportQuestionObj.columnSign == '$' ? summaryReportQuestionObj.columnSign + ' ' + this.utilityService.formatNumberWithCommas(Math.floor(summaryReportQuestionObj.top20Percent), false) : summaryReportQuestionObj.top20Percent + ' ' + summaryReportQuestionObj.columnSign))
        ]
    };
});

let faRows = new FormArray([]);
for (var i = 0, len = this.rows.length; i < len; i++) {
    let row = this.rows[i].cells;
    for (var j = 0, length = this.rows[i].cells.length; j < length; j++) {
        let fc = new FormControl(this.rows[i].cells[j]);
        faRows.push(fc);
    }
}

// this.form.setValue({rows: faRows});
// this.form.setControl('rows', faRows);

In the html:

<tbody>
    <tr *ngFor="let row of rows;let r = index" [attr.data-index]="r">
        <ng-container>
            <td *ngFor="let cell of row.cells;let i = index" [attr.data-index]="i">
                <span *ngIf="i != 2">{{cell}}</span>
                <!-- <input type="text" formControlName="form.rows[r]" *ngIf="i == 2"> -->
            </td>
        </ng-container>
    </tr>
</tbody>

Based on a comment and an answer I've clarified my intention and code. [thank you @amal and @DeborahK] I am now only tying one cell of data from each row to a FormControl, with the other cells in each row displayed with: {{cell}}

Therefore I need to render the contents of this.rows (as shown in html) as well as this.form.rows.

I am currently having two problems understanding how to accomplish this. In the typescript, leaving the final block of loops iterating over the rows of data that create the rows' FormControls the array faRows contains precisely what I expect, a FormArray of FormControls, and each has retrieved it's correct associated saved value. But when attempting to copy data from this.rows to create the FormControls for form.rows, I'm not able to get either setValue or setControl to work correctly. This may be related to the naming of the FormControl on the html page, which is my second issue.

I'm not clear on how to reference the form variable to dynamically create the formControlName in html. This is what I imagine, but it doesn't work.

 <input type="text" formControlName="form.rows[r]" *ngIf="i == 2">

Page rendered as template driven form

Answer

DeborahK picture DeborahK · Sep 30, 2017

As far as I know, it is not possible to call .patchValue with a FormArray.

My form is set up similarly, where tags is my FormArray.

    this.productForm = this.fb.group({
        productName: ['', [Validators.required,
                           Validators.minLength(3),
                           Validators.maxLength(50)]],
        productCode: ['', Validators.required],
        starRating: ['', NumberValidators.range(1, 5)],
        tags: this.fb.array([]),
        description: ''
    });

When setting the data for an edit, I had to do this:

    this.productForm.patchValue({
        productName: this.product.productName,
        productCode: this.product.productCode,
        starRating: this.product.starRating,
        description: this.product.description
    });
    this.productForm.setControl('tags', this.fb.array(this.product.tags || []));

I used patchValue for each of the simple FormControls and used setControl for the FormArray.

You could give that a try and see if that resolves your issue.