2

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 Answers 2

1

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

Sign up to request clarification or add additional context in comments.

12 Comments

my province_id is the foreign key in the district table, and the districts must be loaded based on province_id.
What I provided was a mock service. With the little information you have provided its practically impossible to help you. Could you share a stackblitz demo?
thank you, your information was excellent and my code is completely working, but the problem is that I have two tables province and district and the province id is the foreign key in the district table, when I change the last province of the drop-down list my all-district dropdowns are filling by the last province-related districts.
can you share your service code? especially getProvince and getDistrict functions
getProvinces() { return this.http.get(this.apiURL+ '/api/provinces/'); } getDistrict(province_id:any) { return this.http.get(this.apiURL+ '/api/district/' + province_id); }
|
0

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)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.