1
\$\begingroup\$

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:

  1. Do I have any memory leaks?
  2. 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))
  1. Why is it if I remove the {emitEvent: false} and put in a debounceTime as above the total stops working altogether?
  2. 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() {
  }

}
```
\$\endgroup\$
1
+50
\$\begingroup\$
  1. Do I have any memory leaks?
No
  1. 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
}
  1. 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.

  1. 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

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.