13

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

3
  • Just by looking at the code, I have a feeling that your assignments to the initialize the formArray is not right. Are you expecting to have a formArray control for each of the elements within 1 cell? Like for questionId, question, columnSign, budget etc..? Commented Sep 29, 2017 at 18:55
  • @amal: I've revised the original code because I do not intend to have a formArray control for each element within 1 row. Instead, only 1 element (the third, actual) will have an associated FormControl. Can you please explain how I mix the two arrays, one reactive one not? Commented Oct 3, 2017 at 23:55
  • Not still clear on what you want to do and what you are doing wrong. If you want to refer to a reactive formArray control to the template you need to also refer to and use formArrayName. Could you create a reproduction or show how you want this page to look like in the end (what you are aiming to achieve)? Commented Oct 4, 2017 at 14:10

1 Answer 1

55

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.

Sign up to request clarification or add additional context in comments.

2 Comments

FYI - patchValue does work on FormArray, but it will not resize the array so it only works if the data you're setting is the same length as the FormArray. (not really helpful for this question, but something to note)
This is really helpful. I have nested formgroups, which includes formarrays etc. and is actually a rather complex form. I was getting all sorts of errors and strange behaviour which included formarrays not updating and controls becoming invalid or marked as required, even though they are not. This answer helped me to resolve it. In the end I used patchValue, but then had to loop through all the controls and use SetControl for each nested control.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.