NellaGnute
NellaGnute

Reputation: 335

How do I set FormControl names from a static array rather than from dynamic user input?

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" }

Upvotes: 1

Views: 8628

Answers (2)

Keenan Diggs
Keenan Diggs

Reputation: 2342

Looks like my post last week was deleted because it lacked a proper introduction to the stackblitz example. So, here it is again:

When you say 'set FormControl names', I am assuming you want to build your form with the array data in the model, instead of literally renaming the controls after the form is built.

One way to build the form from your model data would be to iterate over the array, using a string (or a property converted to a string) on each item as the control name. I recommend using a FormGroup instead of a FormArray for this, as a FormArray implicitly uses the ordinal number of each of its FormControl as the name. So, you cannot have named FormControls in a FormArray by conventional means.

I would further recommend building the FormGroup during the OnInit() Angular life cycle hook. It runs after the component is built, accounting for the case your array data is passed in as an @Input before the component is built. If you are using @Input data that is not defined until after its component is built, you will need to use the OnChanges() hook instead.

form: FormGroup;

ngOnInit() {
    this.form = this.buildForm();
}

buildForm(): FormGroup {
  const group = new FormGroup({});

  this.specimens.forEach((specimen: any) => {
    group.addControl(specimen.name, new FormControl());
  })

  return group;
}

Check out this stackblitz for the full example: https://stackblitz.com/edit/angular-dbt5f3

Upvotes: 0

DeborahK
DeborahK

Reputation: 60518

I don't see anywhere you are creating a FormControl?

Try changing this:

control.push(this.formBuilder.control(specimen));

To this:

control.push(new FormControl(specimen.name));

This also works:

control.push(this.formBuilder.control(specimen.name));

I did a stackblitz here: https://stackblitz.com/edit/angular-w4q1w1

Upvotes: 1

Related Questions