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?
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>