Search code examples
angulartypescriptdependenciesdropdownangular-reactive-forms

get dependent values on change of one select box in reactive form with add multiple rows


I want to create dependent drop-down list with add more feature. I tried the below code:

Stackblitz

Basically this should be the flow:

  1. on select of type, name values will be in second dropdown related to selected type
  2. On select of name values, description values will be listed related to selected name

There are multiple row, all row will have own type, name and description values. the issue is when I complete first row selection with all values of 3 dropdowns and click on add more then in second row I select type to change the name values but it also changes the first row values. It should not change the value of first row.

I am not getting how to do this according to row added, also all values are populating from API so I can't call api in stackblits.


Solution

  • I must supose your nameList and descList are not an array of string else an array of object (*) So I imagine some like

    this.nameList = [
     {id:1,typeId:1,name:"Name 1"}, 
     {id:2,typeId:1,name:"Name 2"},
     {id:3,typeId:2,name:"Name 1"}
    ];
    this.descList = [
     {id:1,nameId:1,desc:"Description 1"}, 
     {id:2,nameId:1,desc:"Description 2"},
     {id:3,nameId:2,desc:"Description 3"}
    ];
    

    So it's easy "filter" the options. But before declare a getter to acces the formArray

      get uomArray()
      {
        return this.productForm.get('uom') as FormArray
      }
    

    And change the function to add a new FormGroup

      addmore() {
        this.uomArray.push(
          this.uom()
        );
      }
    

    See how the .html becomes. the "formArrayName" is in a div alone, the [formGroupName]="i" is in the div you iterate nad you get the "value" of the array as uomArray.value[i].type I remove the classes to be more clear the code

    <form name="productForm" autocomplete="off" [formGroup]="productForm">
      <div formArrayName="uom">
        <div *ngFor="let item of uomArray.controls; let i = index"
          [formGroupName]="i">
          {{i+1}}
          <select formControlName="type">
            <option *ngFor="let list of typeList" [value]="list.tyopeId">
                 {{ list.typeName }}
             </option>
          </select>
          <select formControlName="name">
            <ng-container *ngFor="let list of nameList">
              <option *ngIf="list.typeId==uomArray.value[i].type" [value]="list.id">
                {{ list.name }}</option>
              </ng-container>
            </select>
            <select formControlName="desc">
              <ng-container *ngFor="let list of descList">
                <option *ngIf="list.nameId==uomArray.value[i].name" [value]="list.id">
                  {{ list.desc }}
                </option>
              </ng-container>
            </select>
          </div>
        </div>
    </form>
    

    NOTE: not use the array "controls" of a form array or a formGroup, like your this.productForm.controls["uom"]["controls"] //<--WRONG to get or iterate the array, the correct way is use this.productForm.get('uom')

    Update from your comment, I need supoose you has a service that, according the value of "type" you has a list of nameList and according to the value of "name" you has a list of descList. But the only way is that you has an array of observables, really two (one for types and another one names), and subscribe to changes. The first is transform your function uom()

    I supose then that you has a dataService that has two functions

    getNames(res){...}
    getDescriptions(res){...}
    
    names$:Observables[]=[];
    descriptions$:Observables[]=[];
    
    uom() {
       const index=this.uomArray().controls?this.uomArray().controls.length:0
       const group=this.formBuilder.group({
          type: ["", [Validators.required]],
          name: ["", [Validators.required]],
          desc: ["", [Validators.required]]
        });
        names$[index]=group.get('type').valueChange.pipe(
          map(res=>this.dataService.getNames(res)
        )
        descriptions$[index]=group.get('name').valueChange.pipe(
          map(res=>this.dataService.getDescriptions(res)
        )
        return group;
      }
    

    And you iterate over the observables using pie async

    <select formControlName="name">
      <option *ngFor="let item of names$[i]|async">{{item}}</option>
    </select>
    <select formControlName="desc">
      <option *ngFor="let item of descriptions$[i]|async">{{item}}</option>
    </select>