angulartypescriptangular-reactive-formsformarrayangular-ngselect

Angular Reactive Form - ng-select in form array works remotely but fails when it's online


What I expect to achieve is selecting a product that comes with a price, adding the quantity, and then selecting another product still from that same list of products. The challenge is this works as expected on my local machine but when I push the changes online, it duplicates the first selected product on every addItem() function.

I can't tell what I'm missing.

This is my component:

pform: FormGroup;
iform:FormGroup;

createDataForm() {
  this.pform = this.fb.group({
    amount: [''],
    issueDate: ['', Validators.required],
    profileId: ['', Validators.required],
    items: this.fb.array([
      this.iform = this.fb.group({
      product: [{ value: {}, disabled:false}],
      quantity: [0, Validators.min(1)],
      price: [''],
      total: ['']
      })
    ])
  })
}

get items() {
  return this.pform.get('items') as FormArray;
}

addItem() {
  this.items.push(this.fb.group({
    product: [{ value: {}, disabled:false}],
    quantity: [0, Validators.min(1)],
    price: [''],
    total:['']
  }));
  this.calculateTotal();
}

removeItem(index: number): void {
  this.items.removeAt(index)
  this.calculateTotal()
}

calculateTotal() {
  let sum = 0;
  this.items.controls.forEach(control => {
    sum += control.value.total;
  });
  this.pform.patchValue({ amount:sum });
  console.log(sum)
}

// This will calculate each product Total
calItemTotal(control: FormGroup) {
  const quantity = control.value.quantity;
  const price = control.value.price;
  control.patchValue({ total: quantity * price });
  // this.calculateTotal();
}

compareFn(product: Product, _product: Product) {
  return product && _product ? product.id === _product.id : product === _product;
}

// this puts the price for every selected product
onSelectProduct(event: any, index: number) {
  const selectedProduct = event;
  this.items.at(index).get('price').setValue(selectedProduct.price);
}

This is my HTML:

<div formArrayName="items">
  <div *ngFor="let item of items.controls; let i = index">
    <hr/>
    <h5>Product {{i + 1}}</h5>
    <div  [formGroup]="iform">
      <div class="row">

        <!-- Product -->
        <div class="col-md-4">
          <div class="form-group row">
            <label class="col-sm-3 col-form-label" for="product">Product</label>
            <div class="col-sm-9">
              <ng-select
              formControlName="product"
              [compareWith]="compareFn"
              [formControl]="item?.get('product')"
              [items]="products"
              (change)="onSelectProduct($event, i)"
              bindLabel="product"
              bindValue="product">
              </ng-select>
            </div>
          </div>
        </div>

          <!-- Price -->
          <div class="col-md-1">
            <div class="form-group row">
              <!-- <label class="col-xs-3 col-form-label" for="price">P</label> -->
              <div class="col-xs-6 offset-1">
                <input type="number" class="form-control"
                id="price" placeholder="0"
                formControlName="price" [formControl]="item?.get('price')" readonly>
              </div>
            </div>
          </div>

        <!-- Qty -->
        <div class="col-md-3">
          <div class="form-group row">
            <label class="col-xs-2 offset-1 col-form-label" for="quantity">Qty</label>
            <div class="col-xs-6 offset-1">
              <input type="number" class="form-control"
              id="quantity" placeholder="0"
              formControlName="quantity" [formControl]="item?.get('quantity')" (change)="calItemTotal(item)">
            </div>
          </div>
        </div>

        <!-- Total -->
        <div class="col-md-3">
          <div class="form-group row">
            <label class="col-xs-3 col-form-label" for="total">Amt</label>
            <div class="col-xs-6 offset-1">
              <input type="number" class="form-control"
              id="total" placeholder="0"
              formControlName="total" [formControl]="item?.get('total')" readonly>
            </div>
          </div>
        </div>



        <!-- Button -->
        <div class="col-md-1">
          <div class="form-group row">
            <span (click)="removeItem(i)" class="btn btn-sm btn-warning btn-rounded btn-fw"><span><i class="icofont icofont-trash"></i></span></span>
          </div>
        </div>

      </div>
    </div>

  </div>
</div>

Solution

    1. You share the same iform FormGroup instance in the FormArray. Each item should have its own FormGroup instance via [formGroupName]="i" instead of [formGroup]="iform".

    2. Using [formControlName] is duplicate (functionality) with [formControl]. You should use either one but not both. And be careful when using [formControl] as you must handle the type correctly by specifying it as FormControl type otherwise you may get the compilation error when you are enabling strict mode for strict type-checking in tsconfig.json.

    3. Create a function (createItemFormGroup) for creating the item FormGroup instance so you don't need to duplicate it everywhere.

    <div [formGroup]="pform">
      <div formArrayName="items">
        <div *ngFor="let item of items.controls; let i = index">
          <hr />
          <h5>Product {{i + 1}}</h5>
          <div [formGroupName]="i">
            <div class="row">
              <!-- Product -->
              <div class="col-md-4">
                <div class="form-group row">
                  <label class="col-sm-3 col-form-label" for="product"
                    >Product</label
                  >
                  <div class="col-sm-9">
                    <ng-select
                      formControlName="product"
                      [compareWith]="compareFn"
                      [items]="products"
                      (change)="onSelectProduct($event, i)"
                      bindLabel="product"
                      bindValue="product"
                    >
                    </ng-select>
                  </div>
                </div>
              </div>
    
              <!-- Price -->
              <div class="col-md-1">
                <div class="form-group row">
                  <!-- <label class="col-xs-3 col-form-label" for="price">P</label> -->
                  <div class="col-xs-6 offset-1">
                    <input
                      type="number"
                      class="form-control"
                      id="price"
                      placeholder="0"
                      formControlName="price"
                      readonly
                    />
                  </div>
                </div>
              </div>
    
              <!-- Qty -->
              <div class="col-md-3">
                <div class="form-group row">
                  <label class="col-xs-2 offset-1 col-form-label" for="quantity"
                    >Qty</label
                  >
                  <div class="col-xs-6 offset-1">
                    <input
                      type="number"
                      class="form-control"
                      id="quantity"
                      placeholder="0"
                      formControlName="quantity"
                      (change)="calItemTotal(item)"
                    />
                  </div>
                </div>
              </div>
    
              <!-- Total -->
              <div class="col-md-3">
                <div class="form-group row">
                  <label class="col-xs-3 col-form-label" for="total">Amt</label>
                  <div class="col-xs-6 offset-1">
                    <input
                      type="number"
                      class="form-control"
                      id="total"
                      placeholder="0"
                      formControlName="total"
                      readonly
                    />
                  </div>
                </div>
              </div>
    
              <!-- Button -->
              <div class="col-md-1">
                <div class="form-group row">
                  <span
                    (click)="removeItem(i)"
                    class="btn btn-sm btn-warning btn-rounded btn-fw"
                    ><span><i class="icofont icofont-trash"></i></span
                  ></span>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
    
    createDataForm() {
      this.pform = this.fb.group({
        amount: [''],
        issueDate: ['', Validators.required],
        profileId: ['', Validators.required],
        items: this.fb.array([
          this.createItemFormGroup()
        ]),
      });
    }
    
    createItemFormGroup() {
      return this.fb.group({
        product: [{ value: {}, disabled: false }],
        quantity: [0, Validators.min(1)],
        price: [''],
        total: [''],
      })
    }
    
    addItem() {
      this.items.push(
        this.createItemFormGroup()
      );
      this.calculateTotal();
    }
    

    Demo @ StackBlitz