Search code examples
angulartypescriptangular-reactive-formsformarray

How to update reactive form field value when other field value changes


I have below reactive form where I'm using form builder with group.

Fig: enter image description here

Here is the HTML code of the component

<div class="">    
    <form [formGroup]="FeedBack" (ngSubmit)="onSubmit()">
        <div
            class="grid grid-cols-6 gap-x-16 py-5 px-4 text-sm text-gray-700 border-b border-gray-200 dark:border-gray-700">
            <div class="text-gray-500 dark:text-gray-400 flex">
                Number of Cases <input formControlName="caseCount" type="text" id="case-count"
                    class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500">
            </div>
            <div class="text-gray-500 dark:text-gray-400 "></div>
            <div class="text-gray-500 dark:text-gray-400"></div>
            <div class="text-gray-500 dark:text-gray-400"></div>
            <div class="text-gray-500 dark:text-gray-400"></div>

            <div>
                <button type="button" (click)="addNewRow()"
                    class="text-white block w-full bg-blue-600 hover:bg-blue-700 focus:ring-4 focus:ring-blue-200 font-medium rounded-lg text-sm px-4 py-2.5 text-center dark:focus:ring-blue-900">Add</button>
            </div>
        </div>

        <table class="w-full text-sm text-left text-gray-500 dark:text-gray-400">
            <thead class="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400">
                <tr>
                    <th scope="col" class="py-3 px-6">Property</th>
                    <th scope="col" class="py-3 px-6">Group</th>
                    <th scope="col" class="py-3 px-6">Variable Name</th>
                    <th scope="col" class="py-3 px-6">Min Value</th>
                    <th scope="col" class="py-3 px-6">Max Value</th>
                    <th scope="col" class="py-3 px-6"></th>
                </tr>
            </thead>
            <tbody formArrayName="Rows">
                <tr *ngFor="let obj of formArr.controls; let i = index; let l = last" [formGroupName]="i"
                    class="bg-white border-b dark:bg-gray-800 dark:border-gray-700">
                    <td class="py-4 px-6">
                        <select formControlName="property" (change)="selectPropertyChangeHandler($event)">
                            <option *ngFor="let property of propertyList; " value="{{property.id}}">
                                {{property.pname}}
                            </option>
                        </select>
                    </td>
                    <td class="py-4 px-6">
                        <select formControlName="group" (change)="selectGroupChangeHandler($event)">
                            <option *ngFor="let group of groupList; " value="{{group.id}}">
                                {{group.gname}}
                            </option>
                        </select>
                    </td>
                    <td class="py-4 px-6">
                        <input formControlName="variableName" type="text" readonly id="variable-name"
                            class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500">
                    </td>
                    <td class="py-4 px-6">
                        <input formControlName="minValue" type="text" id="min-input"
                            class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500">
                    </td>
                    <td class="py-4 px-6">
                        <input formControlName="maxValue" type="text" id="max-input"
                            class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500">
                    </td>
                    <td class="py-4 px-6">
                        <button type="button" (click)="deleteRow(i)" *ngIf="FeedBack.value.Rows.length > 1">
                            <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
                                stroke="currentColor" class="w-6 h-6">
                                <path stroke-linecap="round" stroke-linejoin="round"
                                    d="M9.75 9.75l4.5 4.5m0-4.5l-4.5 4.5M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
                            </svg>
                        </button>
                    </td>
                </tr>
            </tbody>
        </table>

        <div
            class="grid grid-cols-5 gap-x-16 py-5 px-4 text-sm text-gray-700 border-b border-gray-200 dark:border-gray-700">
            <div class="text-gray-500 dark:text-gray-400"></div>
            <div class="text-gray-500 dark:text-gray-400"></div>
            <div class="text-gray-500 dark:text-gray-400"></div>
            <div class="text-gray-500 dark:text-gray-400"></div>
            <div>
                <button type="submit"
                    class="text-white block w-full bg-blue-600 hover:bg-blue-700 focus:ring-4 focus:ring-blue-200 font-medium rounded-lg text-sm px-4 py-2.5 text-center dark:focus:ring-blue-900">Submit</button>
            </div>
        </div>
        <pre>{{ FeedBack.value | json }}</pre>
    </form>
</div>

The component.ts file for the html form is as follows.

import { Component, OnInit } from '@angular/core';
import { FormGroup, FormBuilder, FormArray, FormControl } from '@angular/forms';
import { HttpClient, HttpHeaders } from '@angular/common/http'

interface APIResponse {
  data: [];
  message: string;
  status: boolean;
}

@Component({
  selector: 'app-home',
  templateUrl: './home.component.html',
  styleUrls: ['./home.component.css']
})
export class HomeComponent implements OnInit {

  FeedBack!: FormGroup;

  ngOnInit(): void {
    this.buildGroupList()
  }

  constructor(private http: HttpClient, private formBuilder: FormBuilder) {
    this.addDefaultRow();
  }

  addDefaultRow() {
    this.FeedBack = this.formBuilder.group({
      Rows: this.formBuilder.array([this.initRows()]),
      caseCount: new FormControl(null)
    });
  }

  initRows() {
    return this.formBuilder.group({
      property: [''],
      group: [''],
      variableName: [],
      minValue: [0],
      maxValue: [0]
    });
  }

  get formArr() {
    return this.FeedBack.get('Rows') as FormArray;
  }

  addRow(obj: any) {
    return this.formBuilder.group({
      property: [obj.property],
      group: [obj.group],
      variableName: [obj.variableName],
      minValue: [obj.minValue],
      maxValue: [obj.maxValue]
    });
  }

  addNewRow() {
    let obj1 = {
      property: '',
      group: '',
      variableName: '',
      minValue: 0,
      maxValue: 0
    };
    this.formArr.push(this.addRow(obj1));
  }

  deleteRow(index: number) {
    this.formArr.removeAt(index);
  }

  onSubmit() {
    console.log('Your form data : ', this.FeedBack.value);
  }

  private buildGroupList() {
    this.http.get<APIResponse>("http://127.0.0.1:5000/mrg_groups").subscribe((res) => {
      let groupList: { id: string, gname: string }[] = [{ id: '', gname: 'Select Group' },]
      res['data'].forEach((group) => {
        groupList.push({ id: group, gname: group })
      });
      this.groupList = groupList
      console.log(this.groupList)
    })
  }

  groupList: { id: string, gname: string }[] = [];

  propertyList: { id: string, pname: string }[] = [
    { id: '', pname: 'Select Property' },
    { id: 'AAA', pname: 'PROP A3' },
    { id: 'BBB', pname: 'PROP B3' },
    { id: 'CCC', pname: 'PROP C3' },
    { id: 'DDD', pname: 'PROP D3' },
    { id: 'EEE', pname: 'PROP E3' }
  ];

  selectedProperty: string = '';
  selectPropertyChangeHandler(event: any) {
    this.selectedProperty = event.target.value;
    console.log('this.selectedProperty: ', this.selectedProperty)
  }

  selectedGroup: string = '';
  selectGroupChangeHandler(event: any) {
    this.selectedGroup = event.target.value;
    console.log('this.selectedGroup: ', this.selectedGroup)
  }
}

The challenge I'm facing is my "VARIABLE NAME" column value is the concatenation of the property field & group field. So whenever the user selected both the drop-down the "VARIABLE NAME" field should automatically be updated by an updated value like "AAA_S01P"(for the first row).

Below is the event listening functions for both the drop-down button where I'm trying to do this.

selectedProperty: string = '';
  selectPropertyChangeHandler(event: any) {
    this.selectedProperty = event.target.value;
    console.log('this.selectedProperty: ', this.selectedProperty)
  }

  selectedGroup: string = '';
  selectGroupChangeHandler(event: any) {
    this.selectedGroup = event.target.value;
    console.log('this.selectedGroup: ', this.selectedGroup)
  }

I tried a couple of techniques like "setValue/updateValue/patchValue" to do that. But, nothing is working. Maybe I'm doing something wrong somewhere. Need some guidance.


Solution

  • As your variableName FormControl is the control of FormGroup within the FormArray, to perform either of setValue/updateValue/patchValue methods, you must remember to provide the row index to determine which FormGroup should be used.

    In my opinion:

    1. As you are using Reactive form, pass the event (for the selected value) for selectPropertyChangeHandler and selectGroupChangeHandler methods can be replaced. You can get the values by specifying the FormControl with the rowIndex.

    2. selectPropertyChangeHandler and selectGroupChangeHandler can be combined into one (I named it selectPropertyOrGroupChangeHandler).

    selectPropertyOrGroupChangeHandler(rowIndex: number) {
      let formGroup = this.formArr.controls[rowIndex] as FormGroup;
      let property = formGroup.controls['property'].value;
      let group = formGroup.controls['group'].value;
    
      if (property && group)
        formGroup.controls['variableName'].patchValue(`${property}_${group}`);
    }
    

    And apply the method in view:

    <select
      formControlName="property"
      (change)="selectPropertyOrGroupChangeHandler(i)"
    >
      ...
    </select>
    
    <select
      formControlName="group"
      (change)="selectPropertyOrGroupChangeHandler(i)"
    >
      ...
    </select>
    

    Demo @ StackBlitz