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);
              });
          }
Owen Kelvin
  • 14,054
  • 10
  • 41
  • 74
Emal Hamza
  • 43
  • 8

2 Answers2

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

Emal Hamza
  • 43
  • 8
Owen Kelvin
  • 14,054
  • 10
  • 41
  • 74
  • my province_id is the foreign key in the district table, and the districts must be loaded based on province_id. – Emal Hamza May 02 '21 at 04:34
  • 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? – Owen Kelvin May 02 '21 at 04:42
  • 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. – Emal Hamza May 02 '21 at 04:54
  • can you share your service code? especially `getProvince` and `getDistrict` functions – Owen Kelvin May 02 '21 at 05:40
  • getProvinces() { return this.http.get(this.apiURL+ '/api/provinces/'); } getDistrict(province_id:any) { return this.http.get(this.apiURL+ '/api/district/' + province_id); } – Emal Hamza May 02 '21 at 05:41
  • Let me have a look – Owen Kelvin May 02 '21 at 05:44
  • Please check the updated answer and stackblitz demo – Owen Kelvin May 02 '21 at 06:14
  • Thank you so much, your code worked correctly. – Emal Hamza May 02 '21 at 06:53
  • Can you help me with the Edit of this form? – Emal Hamza May 03 '21 at 10:31
  • Sorry? Edit of which form? – Owen Kelvin May 03 '21 at 10:34
  • I want to edit a record, I have used the following function in my component and I want to assign selected data into formArray. ngOnInit() { this.contactService.getData(this.id).subscribe(res=>{ this.itemRows = res; }) and this my function inside servivce. getData(id:number){ return this.http.get(this.apiURL + '/api/contacts/edit/' + id); } – Emal Hamza May 04 '21 at 05:12
  • Hello, can you see my code for update it is not working correctly? https://stackoverflow.com/questions/67396785/edit-cascading-dropdown-in-dynamic-row-angular – Emal Hamza May 10 '21 at 07:29
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)

Eliseo
  • 50,109
  • 4
  • 29
  • 67