Change detection not working when updating FormArray inside FormGroup

Viewed 4481

Using reactive forms and change detection on push.

I have been searching all over SO for how to do this example I feel like this s fairly common, however, I can't seem to find a good example of how to properly do this.

When attempting to update a FormArray that contains a list of FormGroup inside of a subscribe inside ngOnInit the FormArray object is updated but unless change detection is forced on the component the view will not update the FormArray groups. All other controls that are updated in this manner are working. So it feels like however I'm trying to update the FormArray is wrong and that I should not and don't want to force change detection.

import { Component, OnInit, ChangeDetectionStrategy } from "@angular/core";
import { JournalTransactionService } from "app/accounting/shared-services/journal-transaction.service";
import { JournalEntry } from "accountingModels";
import { FormBuilder, FormGroup, FormArray } from "@angular/forms";
import { EntryLine } from "app/accounting/models/journal";

@Component({
  selector: "general-journal",
  templateUrl: "./general-journal.component.html",
  styleUrls: ["./general-journal.component.scss"],
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class GeneralJournalComponent implements OnInit {
  journalEntry: JournalEntry;
  journalEntryForm: FormGroup;

  constructor(private _journalTransactionService: JournalTransactionService, private _formBuilder: FormBuilder) { }

  get entryLines(): FormArray {
    return <FormArray>this.journalEntryForm.get("entryLines");
  };

  ngOnInit() {
    this.journalEntryForm = this._formBuilder.group(
      {
        transactionId: "",
        transactionDate: "",
        autoReversalDate: "",
        journalEntryType: "",
        entryLines: this._formBuilder.array([])
      }
    );
    this._journalTransactionService.getTransaction("", 80).subscribe(journalEntry => {
      journalEntry.entryLines.forEach(entryLine => {
        this.entryLines.push(this.entryLineMapper(entryLine));
      });

      this.journalEntryForm.patchValue(
        {
          transactionId: journalEntry.transactionId,
          transactionDate: journalEntry.transactionDate,
          autoReversalDate: journalEntry.autoReversalDate,
          journalEntryType: journalEntry.journalEntryType
        }
      );
    });
  }


  private entryLineMapper = (entryLine: EntryLine): FormGroup => {
    return this._formBuilder.group({
      accountId: entryLine.accountId,
      controlId: entryLine.controlId,
      debit: entryLine.debit,
      credit: entryLine.credit,
      description: entryLine.description,
    })
  }
}

The relevant section of the html. If you type into any other field that is bound such as the transaction id then change detection is run and the table is filled out. If you manually trigger change detection after patch value in the subscribe block the table is filled out.

  <tbody formArrayName="entryLines">
      <tr *ngFor="let entryLine of entryLines.controls; index as i" [formGroupName]="i">
        <td>
          <input type="text" class="form-control" formControlName="accountId">
        </td>
        <td>
          <input type="text" class="form-control" formControlName="controlId">
        </td>
        <td>
          <input type="text" class="form-control" formControlName="debit">
        </td>
        <td>
          <input type="text" class="form-control" formControlName="credit">
        </td>
        <td>
          <input type="text" class="form-control" formControlName="description">
        </td>
      </tr>
    </tbody>

Update

To get around this issue for now I'm creating two components one that is the base component for a feature and one that is wrapping the form. This is allowing me to pass the data for building the form as an input to the form component thus allowing OnPush detection to work. I'm sort of okay with this but it still feels like a FormArray should be able to have FormGroups or controls added via a subscription and have the new values updated in the UI. I think if the FormArray controls was an observable of AbstracControl this would work. If anyone has a better solution let us all know.

2 Answers

don't use map instead just use push.

private entryLineBuilder = (entryLines: EntryLine[]): FormArray => {
    return this._formBuilder.array(entryLines.push(this.entryLineMapper));
  }

Because you're changing the array by pushing, that should in turn trigger angulars change detection.

Not sure if you find the answer yet. Use removeAt will trigger the change detection in Angular 8.

Related