2
votes

I have cascading dropdown list which works alone, but when I generate a new row and change one of my dropdowns its affects my entire dropdown list.

Here is my html code.

    <td>       
       <select class="form-control" formControlName="province_id" 
           (change)="onChangeProvince($event)">
          <option >Select Province</option>
          <option *ngFor="let province of provinces; let i = index" 
              [value]="province.id">{{province.province_name}}</option>
       </select>
    </td>
    <td>
       <select  class="form-control" formControlName="district_id">
          <option >Select District</option>
          <option *ngFor="let district of districts" [value]="district.id">
               {{district.district_name}}</option>
       </select>
    </td>
    <td>
       <button class="btn btn-danger"  type="button" 
           (click)="deleteMyRelation(i)">Delete</button>
       <button class="btn btn-primary" type="button" 
           (click)="addItem()">Add</button>
    </td>

Here is my component code.

      createItem(): FormGroup {
            return this.fb.group({
              province_id: '',
              district_id: ''
            });
          }
        
       addItem(): void {
            this.itemRows = this.contactForm.get('itemRows') as FormArray;
            this.itemRows.push(this.createItem());
          }

       ngOnInit() {
            this.contactForm = this.fb.group({
              province_id: '',
              district_id: '',
              itemRows: this.fb.array([ this.createItem() ])
            });
            this.getProvinces();
          }

          provinces = <any>[];
          districts = <any>[];

          getProvinces(){
            this.contactService.getProvinces().subscribe(
              data => {
                this.provinces = data;
                console.log(data);
              },
              error => {
                console.log(error);
              });
          }
          onChangeProvince(event:any){
            
            this.contactService.getDistrict(event.target.value).subscribe(
              data => {
                this.districts = data;
              },
              error => {
                console.log(error);
              });
          }
2

2 Answers

1
votes

You need define each control with a [formGroupName]='i'. Now Angular will assign the values to each group properly, something like

<table [formGroup]='contactForm'>
  <ng-container formArrayName='itemRows'>
    <tr *ngFor="let itemRow of itemRows.controls; let i = index" [formGroupName]='i'>
      <td>
        <select class="form-control" formControlName="province_id"
           (change)="onChangeProvince(i)">
          <option  value=''>Select Province</option>
          <option *ngFor="let province of provinces; let i = index" 
              [value]="province.id">{{province.province_name}}</option>
       </select>
      </td>
      <td>
        <select  class="form-control" formControlName="district_id">
          <option value=''>Select District</option>
          <option *ngFor="let district of districts[i]" [value]="district.id">
               {{district.district_name}}</option>
       </select>
      </td>
      <td>
        <button class="btn btn-danger"  type="button"
           (click)="deleteMyRelation(i)">Delete</button>
        <button class="btn btn-primary" type="button"
           (click)="addItem()">Add</button>
      </td>
    </tr>
  </ng-container>
</table>

We can now define below functions to enable adding and removing of items

  get itemRows() {
    return this.contactForm.get("itemRows") as FormArray;
  }
  deleteMyRelation(i) {
    this.itemRows.removeAt(i)
  }
  addItem(): void {
    this.itemRows.push(this.createItem());
  }
  onChangeProvince(i) {
    this.contactService
      .getDistrict(this.itemRows.get(i + ".province_id").value)
      .subscribe(
        data => {
          this.districts[i] = data;
        },
        error => {
          console.log(error);
        }
      );
  }

Now your form should work as expected

See Demo

0
votes

Complementary the answer, when the only we need in a subscription use the values in the .html you can use pipe async.

You define

//It's usually way, named a variable that was an observable adding a "$" to the variable
province$=this.contactService.getProvinces()
district$=[]

As always we has a formArray is good has a getter

  get array(){
     return this.contactForm.get('itemRows') as FormArray;  
  }

Your (change) becomes like

  change(index)
  {
    const value=+(this.array.at(index) as FormGroup).get('province_id').value;
    this.array.at(index).get('district_id').setValue(null)
    this.district$[index]=this.dataService.getDistrict(value)
  }

You can use

   <select class="form-control" formControlName="province_id"
                  (change)="change(index)"  >
       <!--see that in selectProvince we put [value]="null" and hidden-->
      <option [value]="null" hidden>Select Province</option>
      <option *ngFor="let province of province$|async; let i = index" 
          [value]="province.id">{{province.province_name}}</option>
   </select>
   <select  class="form-control" formControlName="district_id">
      <option [value]="null" hidden>Select District</option>
      <option *ngFor="let district of district$[index]|async"
           [value]="district.id">
           {{district.district_name}}</option>
   </select>

I write a fool example in this stackblitz (not has your models, but I hope can help for inspiration)