1

I've got a formarray. If I initialize it with a formbuilder and push onto it, it seems to work okay. If I take the same array that I'm pushing items from, and try to add it all at once with the spread operator, I get the error "ERROR Error: Cannot find control with path: 'sizesArray -> 0'"

My question is, in keeping with the spirit of ngrx/rsjx/functional-reactive programming, how can I keep the Formarray 'immutable' and assign to it from the FormGroup[] rather than clearing, looping and pushing?

Here's the relevant code:

sizesArray: FormArray;
sizesArray2: FormArray;
sizes: Observable<SizeModel[]>;

constructor(
    private productStore: Store<productState.State>,
    private fb: FormBuilder
  ) {
    this.sizes = productStore.select(productState.selectSizes);
  }

ngOnInit() {


    this.sizesArray = this.fb.array([]);
    this.sizesArray2 = this.fb.array([]);

    this.sizes
      .pipe(
        skipWhile(sizes => !sizes),
        map(sizes => {
         return sizes.map(size => this.fb.group({size}));
        }
       )
      )
      .subscribe(
        sizes => {
          // this is the code I want to use.  that throws the error
          this.sizesArray = this.fb.array([...sizes]);

          // this code works, but I don't know how it's different than the code above
          this.sizesArray2.clear();
          sizes.forEach( size => {
            this.sizesArray2.push(size);
          });

        }
      );

    this.sizeForm = this.fb.group({
      sizesArray: this.sizesArray
    });

  }

and my template:

...
<form [formGroup]="sizeForm">
      <fieldset  id="size-container">
        <legend>Sizes</legend>
        <div formArrayName="sizesArray" >
            <div *ngFor="let size of sizesArray.controls; let i = index;" [formGroupName]="i">
            <app-size-form
              formControlName="size"
              (deleteClicked)="deleteSize(i);">
            </app-size-form>
          </div>
        </div>
      </fieldset>
      <button (click)="addSize()" id="size-button">Add Size</button>
      <button mat-button matStepperPrevious>Back</button>
      <button mat-button (click)="submit()">Save</button>
    </form>
...

Thanks!

jmiles540
  • 63
  • 8
  • you can not use spread operator with an object so complex as a formGroup, (spread operator give a copy of "properties" but not methods, yes you has,e.g. a "property" setValue, but not a "method" setValue).NOTE: I think that if you use this.fb.array(sizes) it must be work – Eliseo Apr 09 '20 at 14:22
  • @eliseo - good point. I'm planning to bring in lodash's cloneDeep to remedy it. Thanks! – jmiles540 Apr 09 '20 at 18:07

1 Answers1

1

I think the problem is that you're changing the references.

this.sizeForm = this.fb.group({
  sizesArray: this.sizesArray
});

At this point sizeArray points to the value referenced by this.sizesArray, which is this.fb.array([]). Then, in your subscriber's next callback, you're changing the references. This value is also 'captured' by formControlName='sizesArray', meaning that

<div *ngFor="let size of sizesArray.controls; let i = index;" [formGroupName]="i">

Will create a number of FormGroup instances, which are the children of a container sizesArray, which is empty.

Now, this.sizesArray(on which you're iterating over in the template) points to something else, namely: this.fb.array([...sizes]).

So, what's basically happening is that sizesArray(the child of sizeForm) points to an empty FormArray and this.sizesArray points to a non-empty FormArray.

What you could to in this case would be this:

.subscribe(sizes => {
  this.sizeForm.setControl('sizeArray', this.fb.array(sizes))
})
Andrei Gătej
  • 11,116
  • 1
  • 14
  • 31
  • This was exactly it. Thanks! I guess I thought that the form would be referencing my this.sizesArray variable, not the value of that variable, so when I changed it, I figured the form would continue pointing to the variable and the variable would point to the new location. Wrong. I ended up moving the code below: ```this.sizeForm = this.fb.group({ sizesArray: this.sizesArray });``` to a function and caling it again after ```this.fb.array([...sizes])``` and that worked brilliantly. – jmiles540 Apr 09 '20 at 16:08
  • Glad it worked! [Here's](https://indepth.dev/a-thorough-exploration-of-angular-forms/) an article which dives deep into Angular Forms, if you'd like to find out more interesting things about them! – Andrei Gătej Apr 09 '20 at 16:14
  • I've done a little more research on this since, and I guess that coming from a C# background it was the fact that in javascript and typescript objects are passed by reference, but references to objects are passed by value that got me. This is really counterintuitive, and I'm still wrapping my mind around it. The answer to this question gave me some real insights too: https://stackoverflow.com/questions/518000/is-javascript-a-pass-by-reference-or-pass-by-value-language – jmiles540 Apr 10 '20 at 18:10
  • Thanks for sharing! `but references to objects are passed by value` I'm not sure this is true. They way I see it, `sizesArray` pointed to `[]`. And `[]` was used by `sizeForm`. Then `sizesArray` pointer to another array `[...sizes]`, so there is no way the initial `[]` could've been updated. – Andrei Gătej Apr 10 '20 at 18:31