Stackblitz - https://stackblitz.com/edit/angular-plp4rb-formarray-total-prs9ay
I'm new to RxJS observables.
I've created a form array where the total for a line is quantity * rate
. Via valueChanges
for the line form group I set the total via the calcTotal
method.
On form changes I emit all the lines up to parent to calculate the total.
Questions:
- Do I have any memory leaks?
- Why is it if I stick
debounceTime(1)
inside the lineItem valueChanges then this stops working where the calculation is out by the last change?
lineItem.valueChanges.pipe(
takeUntil(this.destroyed$),
debounceTime(1)
).subscribe(value => this.calcTotal(lineItem))
- Why is it if I remove the
{emitEvent: false}
and put in adebounceTime
as above the total stops working altogether? - Should i be using some other way like
combineLatest
? If so how do I collect the valueChanges observables (I'll figure it out from here if this is required)?
import { Component, OnInit, Input, Output, EventEmitter, ViewChild } from '@angular/core';
import { FormArray, FormBuilder, FormControl, FormGroup } from '@angular/forms'
import { Subject } from 'rxjs'
import { takeUntil, debounceTime} from 'rxjs/operators'
import { MatTable } from '@angular/material';
import { Line } from '../app.component';
@Component({
selector: 'app-total-calculator',
templateUrl: './total-calculator.component.html',
styleUrls: ['./total-calculator.component.css']
})
export class TotalCalculatorComponent implements OnInit {
displayedColumns = ['quantity', 'rate', 'total'];
formGroup: FormGroup;
@Input() lines: Line[];
@Output() linesChange = new EventEmitter<Line[]>();
@ViewChild(MatTable) private matTable: MatTable<any>;
get linesArray(): FormArray {
return this.formGroup.get('lines') as FormArray;
}
private destroyed$ = new Subject<void>();
constructor(private fb: FormBuilder) {
this.buildForm();
}
ngOnDestroy() {
this.destroyed$.next();
this.destroyed$.complete();
}
addLineClick() {
this.addLineFormGroup();
}
addLineFormGroup() {
this.linesArray.push(this
.getLineFormGroup());
this.matTable.renderRows();
}
private buildForm() {
// formarray
this.formGroup = this.fb.group({
lines: this.fb.array([])
});
// subscribe to any changes and emit these
this.formGroup.valueChanges.pipe(
takeUntil(this.destroyed$),
debounceTime(300)
).subscribe(value => {
if (!this.formGroup.valid) {
return;
}
this.linesChange.emit(value['lines']);
});
}
getLineFormGroup(): FormGroup {
let lineItem = this.fb.group({
quantity: new FormControl(),
rate: new FormControl(),
total: new FormControl()
});
lineItem.valueChanges.pipe(
takeUntil(this.destroyed$),
).subscribe(value => this.calcTotal(lineItem))
return lineItem
}
calcTotal(line: FormGroup) {
const quantity = +line.controls['quantity'].value;
const rate = +line.controls['rate'].value;
line.controls['total'].setValue((quantity * rate).toFixed(2), {emitEvent: false});
}
ngOnInit() {
}
}
```
1 Answer 1
- Do I have any memory leaks?
No
- Why is it if I stick debounceTime(1) inside the lineItem valueChanges then this stops working where the calculation is out by the last change?
Beacuse debounceTime(1) delays the response by 1ms inside the lineItem and
this.linesChange.emit(value['lines']);
is already run with old total
The implementation below will sort your problems.
getLineFormGroup(): FormGroup {
let lineItem = this.fb.group({
quantity: new FormControl(),
rate: new FormControl(),
total: new FormControl()
});
lineItem.valueChanges.pipe(
takeUntil(this.destroyed$),
debounceTime(20)
).subscribe(value =>
{
console.log("should be called first")
this.calcTotal(lineItem)
if (!this.formGroup.valid) {
return;
}
this.linesChange.emit(this.formGroup.value['lines']);
}
)
return lineItem
}
- Why is it if I remove the {emitEvent: false} and put in a debounceTime as above the total stops working altogether?
The lineItem.valueChanges observable will fire in a circular fashion when you remove this {emitEvent: false}. line.controls['total'].setValue() will fire lineItem.valueChanges again. Which in-turn crashes the app and stop.
- Should i be using some other way like combineLatest? If so how do I collect the valueChanges observables (I'll figure it out from here if this is required)?
I would suggest not required