1
votes

I'm having an angular app with a form and I'm going to use Reactive forms there. In that form there's a dropdown (Items are loading from the API). If user clicks a dropdown item , it generates a table with two fields (Inputs and label). Number of input items are generated dynamically.

Ex: If user clicks the first item of the dropdown , may be 5 input fields and labels will generate. If user clicks the second item of the dropdown , may be 2 input fields and labels will generate. This is what I tried

ngOnInit() {
  this.orderForm = this.formBuilder.group({
    tableInputs: this.formBuilder.array([])
  })

}

This is the dropdown change function (getting values from API)

dropdownChanged(){
const control = <FormArray>this.orderForm.controls['tableInputs'];
this.apiService.listColumnData(this.selectedTable).subscribe(result=>{

  this.columns = result['data'].columns;
  control.push(new FormControl(this.columns))
});  }

this is how 'this.columns look like (getting from API)

[{0: display_name: "sales name" , id: 17, name: "sales_name",}]

This is how I try to render the items in view.

<form>
<div [formGroup]="orderForm">
<div formArrayName="tableInputs">

  <div *ngFor="let item of orderForm.controls.name; let i=index"  >

      <input formControlName="tableInputs">

  </div>
</div>
</div>

I'm new to Angular Reactive forms. I wasn't able to render the items. Could someone help me on this?

2

2 Answers

1
votes

When you get the response, push formgroups to your formarray, not single formcontrol. Also, like mentioned in other answer, clear the formarray in between. If you are using angular 8, you can use clear(), else loop through the formarray for example with a while loop.

I have made a getter for the formarray, looks cleaner in my opinion:

get tableInputs() {
  return <FormArray>this.orderForm.get('tableInputs');
}

Then build the form like you have and modify the dropdownChanged() function to:

dropdownChanged() {
  this.apiService.listColumnData(this.selectedTable).subscribe(result => {
    // clear previous formarray
    this.tableInputs.clear();
    // push new groups to formarray
    result.forEach(x => this.tableInputs.push(this.formBuilder.group(x)));
  });
}

If you need to add validation, you can just destruct it as follows:

  result.forEach(x => this.tableInputs.push(this.formBuilder.group({
    id: [x.id, Validators.required]
    // ....
  })));

Then iterate the formarray in your template and show your fields:

<form [formGroup]="orderForm">
  <div formArrayName="tableInputs">
    <div *ngFor="let item of tableInputs.controls; let i=index" [formGroupName]="i">
      <input formControlName="id">
      <input formControlName="display_name">
      <input formControlName="name">
    </div>
  </div>
</form>
0
votes

As the name suggests, a FormArray expects an array of FormControls. When you handle the response from the api you only add one control (which would result in a second item when another option in the dropdown is selected and so on). Instead you should iterate over the received data (this.columns.forEach(...)).

Furthermore you can't just pass your data to a FormControl to create new ones. I'd recommend creating a function addFormControl(singleColumn) as follows:

const tableInputs = this.orderForm.get("tableInputs") as FormArray;
tableInputs.push(this.formBuilder.group({
    display_name: singleColumn.display_name,
    name: singleColumn.name
}));

Also you probably want to remove the old items in the FormArray in case the api is called several times. Something like this should do the trick:

clearArray() {
    const tableInputs = this.orderForm.get("tableInputs") as FormArray;
    tableInputs = this.formBuilder.array([]);
}