I am attempting to create an input form where the label for the input and the FormControl name come from an array of objects that is defined in the model, rather than generating each new input dynamically from a user action. All the examples I see are for a scenario where a user clicks a button to add a new input, but I don't see any for my use case.
I have tried to use the examples from the official Angular guide here and many I have found from searching elsewhere, but I am unable to figure out how to actually push the information onto the FormArray correctly. The labels are populated correctly and there are the correct number of inputs, but the binding between the model and view is not happening. Here is the closest I've gotten:
TypeScript:
import { FormGroup, FormControl, FormBuilder, FormArray, Validators } from '@angular/forms';
export class LabFormComponent implements OnInit {
specimenControls: FormArray;
constructor(
private router: Router,
private formBuilder: FormBuilder
) { }
async ngOnInit() {
await this.addSpecimens();
}
specimens = [
{ label: "SST: ", name: "sst" },
{ label: "LAV: ", name: "lav" },
{ label: "BLUE: ", name: "blue"}
];
specimenFields : FormGroup = this.formBuilder.group({
specimenControls: this.formBuilder.array([
])
});
addNewFormControl(specimen) {
const control = <FormArray>this.specimenFields.controls['specimenControls'];
control.push(this.formBuilder.control(specimen));
}
addSpecimens() {
for(let specimen of this.specimens) {
this.addNewFormControl(specimen);
}
}
HTML:
<div>
<form [formGroup]="specimenFields" (ngSubmit)="addSpecs();showSpecForm=false">
<div formArrayName="specimenControls">
<div *ngFor="let specimen of specimens; let i=index" style="margin-bottom:10px">
<label class="form-label">{{specimen.label}}</label>
<input matInput class="form-textbox" type="text" [formControlName]="i" maxlength="2" size="2" value="0"/>
<button mat-raised-button style="margin-left:15px;" (click)="decNumber(this.value)" color="primary">-</button>
<button mat-raised-button style="margin-left:10px;" (click)="incNumber(this.value)" color="primary">+</button>
</div>
</div>
<button mat-raised-button type="submit" color="primary">Submit</button>
</form>
</div>
I am expecting that each dynamically generated input will have a two way binding, but instead I am getting an error of:
"Cannot find control with path: 'specimenControls -> 0'"
I have looked this error up all over the place, but as I mentioned before, all the scenarios I see are for a case where a user clicks a button to add a new input. If I use console.log(control) in the addNewFormControl() method I can see the FormControl object, so I know "something" is happening, but I don't know why it's not seeing it. For example:
{…}
_onCollectionChange: function _onCollectionChange()
_onDisabledChange: Array []
_parent: Object { pristine: true, touched: false, status: "VALID", … }
asyncValidator: null
controls: (3) […]
0: {…}
_onChange: Array []
_onCollectionChange: function _onCollectionChange()
_onDisabledChange: Array []
_parent: Object { pristine: true, touched: false, status: "VALID", … }
_pendingValue: Object { label: "SST: ", name: "sst" }
asyncValidator: null
errors: null
pristine: true
status: "VALID"
statusChanges: Object { _isScalar: false, closed: false, isStopped: false, … }
touched: false
validator: null
value: Object { label: "SST: ", name: "sst" }