Search code examples
angularvalidationangular-forms

Angular FormGroup controls property not updated when removing controls


I have a custom validator directive which takes in an angular FormGroup. The controls property mainly updates as expected, but it seems to struggle with controls being removed from the FormGroup. It's my first time using a FormGroup so I don't know if I've implemented it incorrectly.

The basic structure of my page is that the user can select multiple values from a dropdown, and they are added to a table which contains an input element. Everything in this table (i.e. one row per selected value) is in the same FormGroup. If I add 3 rows to the table, then group.controls (correctly) contains name0, name1 and name2. However, if I then remove the 2nd then 3rd selections, then group.controls contains name0 (correct) and name2 (incorrect).

This is the basic structure of the form:

<form #modelsForm="ngForm">

    <div class="form-group" ngModelGroup="testModelGroup" #componentModelsFormGroup="ngModelGroup" [appFnValidate]="testValidator">
        <ng-select [items]="models" bindLabel="name" name="models" #modelSelection="ngModel" [(ngModel)]="selectedModels" (add)="selectModel($event)" (remove)="deselectModel($event)" (clear)="deselectAll()">
    </ng-select>

    <table class="table table-striped table-condensed table-hover table-bordered text-nowrap no-margin">
        <thead>
            <tr>
                <th>Model</th>
                <th>Name</th>
            </tr>
        </thead>
        <tbody>
            <tr *ngFor="let componentModel of component.componentModels; let rowNo = index">
                <td>
                    {{componentModel.modelName}}
                </td>
                <td>
                    <input class="form-control" required #componentModelNameValue="ngModel" [name]="'name' + rowNo" [(ngModel)]="componentModel.name" />
                </td>
            </tr>
        </tbody>
    </table>

  </div>

</form>

The [appFnValidate]="testValidator" code currently just logs out to the console which controls the FormGroup contains.

I've made a stackblitz which allows reproduction. If you open the dev console you'll be able to see the logging from the validator. https://stackblitz.com/edit/angular-form-group-not-updating

Any ideas what I need to do to make the FormGroup controls property update correctly when removing controls from the group?


Solution

  • I realised the root cause of this bug just as I was finishing formatting the question, so I thought I might as well still post it in the hope it benefits someone else in the future!

    This issue was caused by the name of the input control within the table, which was set like this: [name]="'name' + rowNo" where rowNo came from the *ngFor="let componentModel of component.componentModels; let rowNo = index".

    However, as rows are deleted, unless they're the last row, the deletion changes the index of the other rows. This seems to prevent angular from keeping track of everything correctly (presumably because the name gets reused - i.e. if I delete the control named name0 then a different control will be renamed to name0).

    The fix was very simple, just use a better unique identifier for each row in the table! I used componentModel.modelId as I know that's guaranteed to be unique in my scenario. This was the only code which needed changing. The tr tag thus became:

    <tr *ngFor="let componentModel of component.componentModels">
        <td>
            {{componentModel.modelName}}
        </td>
        <td>
            <input class="form-control" required #componentModelNameValue="ngModel" [name]="'name' + componentModel.modelId" [(ngModel)]="componentModel.name" />
        </td>
    </tr>