1

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

2 Answers 2

1

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

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

4 Comments

As I understand it, the FormBuilder class has a method called control() that is the equivalent of new FormControl(), so when you do private formBuilder: FormBuilder and then this.formBuilder.control(_something_) then it's the same thing. Do you know if that's the case?
You are right. I updated my answer. You just need to specify the property, not just the object.
Aha! That wasn't it, but what you provided led me to the answer. It wasn't occurring to me that the value for this.formBuilder.control() is setting the initial value of the input form, not the name of it. Eventually I realized that the array I needed to loop through was not my array, but the control's array. So instead of let specimen of specimens, I needed let specimen of specimenFields.controls.specimenControls.controls instead. I updated the stackblitz to illustrate (although I have no idea why the values are objects now instead of "0"), but it works on mine - Thanks!
0

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

Comments

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.