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>
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"
.
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.
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();
}