Search code examples
angularangular-reactive-forms

Is there a way to three formControlName that share the same property name in a Angular Reactive Form?


I've created a reactive form based on the data I receive from a REST API and also static content. Once the user submits the form the data is sent. I have one section where the property name of a formControl is the same name. e.g.

   <ng-container formArrayName="documentFields">
    <ng-container *ngFor="let doc of documentFields(comIndex).controls; let skillIndex=index">
      <ng-container [formGroupName]="skillIndex">
        <div class="row mb-3">
          <label class="col-sm-1 col-form-label">Category:</label>
          <div class="col-md-2">
            <ng-container>
              <select id="" class="form-select"  formControlName="value">
                <option  selected>Select</option>
                <option *ngFor="let c of taxonomyResponse.categoryList">{{c.name}}</option>
              </select>
            </ng-container>
          </div>
          <label class="col-auto col-form-label">Sub-Category:</label>
          <div class="col-md-2">
            <select id="" class="form-select" formControlName="value">
              <option selected>Select</option>
            </select>
          </div>
        </div>
      </ng-container>
    </ng-container>
  </ng-container>

As you can see "value" is called twice. In order for the POST request to work these property filed need to be the same. Here is my method.

newDocumentFields() {
  return this.fb.group({
      name: '',
      value: '',
    })
}

And this is how the response should look when sending the POST

 "documents": [
  {
    "name": "1.pdf",
    "documentId": 34,
    "documentFields": [
      {
        "name": "",
        "value": ""
      },
      {
        "name": "",
        "value": ""
      }
    ]
]

The "value" will have data from a service call but the "name" will be static.


Solution

  • Technically within a FormGroup/object, you can't have multiple keys with the same name.

    Based on your expected result, within the documentFields array, it will have 2 FormGroup/object(s), the first one will be Category and followed by the Sub-Category.

    You are required to use 2 FormGroups instead of a single FormGroup.

    Your documentFields FormArray structure should be as below:

    <ng-container formArrayName="documentFields">
      <ng-container
        *ngFor="let skillIndex of documentFieldsIndexes(comIndex);"
      >
        <div class="row mb-3">
          <label class="col-sm-1 col-form-label">Category:</label>
          <div class="col-md-2">
            <ng-container [formGroupName]="skillIndex">
              <select id="" class="form-select" formControlName="value">
                <option selected>Select</option>
                <option *ngFor="let c of taxonomyResponse.categoryList">
                  {{c.name}}
                </option>
              </select>
            </ng-container>
          </div>
    
          <label class="col-auto col-form-label">Sub-Category:</label>
          <div class="col-md-2">
            <ng-container [formGroupName]="skillIndex + 1">
              <select id="" class="form-select" formControlName="value">
                <option selected>Select</option>
              </select>
            </ng-container>
          </div>
        </div>
      </ng-container>
    </ng-container>
    

    For the iteration (*ngFor) part, you need to iterate every 2 elements in order to make each iteration will have 2 elements. Thus, there is why the below function is needed with:

    1. Divide the total number of elements within the documentFields array by 2.
    2. Construct an array for the index to be iterated in *ngFor, the values (indexes) will be started from 0 and incremented by 2. Example: 0, 2, 4...
    documentFieldsIndexes(comIndex: number) {
      return Array(this.documentFields(comIndex).controls.length / 2)
        .fill(0)
        .map((x, i) => i * 2);
    }
    

    Demo @ StackBlitz