Search code examples
angularangular2-formsformbuilder

Angular2 Formbuilder - Referencing FormArray Element


We have a form that takes a service response in order to populate a table (our FormArray) with the correct field names. Given the correct form field names, we're trying to populate the fields for some parameters with another service call to a different database.

A simple example:

ngOnInit() {
  this.myForm = this._fb.group({
    name: [''],
    myArray: this._fb.array([])
  }),
  initArray();
}
private initArray(){
  this.arrayValsService.getValues.subscribe(
    response => {
                  for (item of response) {
                    let arrayItemGroup = this.createItemGroup(item.code, item.value);
                    const control = <FormArray>this.myForm.controls['myArray'];
                    control.push(arrayItemGroup);
                  }
                },
    err => {console.error(err);}
  };
}
private createItemGroup(code: string, value: string) {
  return this._fb_group({
    selected: [false],
    itemCode: [code],
    itemValue: [value],
    otherVal1: [''],    
    otherVal2: ['']  
  });
}

private setArray() {
  if(this.key !== undefined) {
    this.dataService.getData(this.key).subscribe(
      response => { 
        this.myForm.patchValue(response);
        for (let resItem of response.itemsSet) { 
          for (let formItem of <FormArray>this.myForm.controls['myArray']) {
            if (formItem.controls.itemCode === resItem.code) { // <== ISSUE HERE
              formItem.patchValue(response.itemsSet.resItem);
              formItem.controls.selected.setValue(true);
            }
          }
        }
      err => {console.error(err);}
    );
  }
}

I'm receiving an error in the console that we 'Cannot read property 'controls' of undefined,' occurring on the marked line above. I need to reference the individual form control groups within the array in order to update their values accordingly. Instead of using the nested for loop, I also tried to use array.find in order to check the specific control within the array element group:

const control = <FormArray>(this.myForm.controls['myArray'])
  .find(item => myArray.controls.item.itemCode === resItem.code)

This is also not working; encountering the same issue with controls being a property of undefined. The bottom line is, I need a way to reference the child elements of a formArray and the controls of each of those children.

The below method works for iterating through the formArray.controls array object, but still produces a typescript compile error:

for (let resItem of response.itemsSet) {
  const control = this.myForm.controls['myArray'].controls
    .find((item:any) => item.value.itemCode === resItem.code);
  if(control) {control.controls.selected.setValue(true); // ...other value updates

The control is an array object, so .find() is working here. Within the children (of type FormGroup) of ['myArray'].controls there is another array object, child.value which has the code that I am comparing the service response with. Functionally this is doing the intended job; however, I'm still receiving an error: 'error TS2329: Property 'controls' does not exist on type 'AbstractControl'.'

Final edit for future reference:

Typecasting the FormArray itself and the FormGroup I'm 'finding' was effective in removing the initial typescript errors. Upon doing this, however, the form controls I was updating started throwing the same "property 'control' on type 'AbstractControl'" error. I changed the format for referencing the specific control based on this question.

Final result:

for (let resItem of response.itemsSet) {
      const control = (<FormGroup>((<FormArray>(this.myForm.controls['myArray'])).controls
        .find((item:any) => item.value.itemCode === resItem.code)));
      if(control) {control.controls['selected']setValue(true); // ...other value updates

Key takeaways: typecasting FormGroups, FormArrays; referencing explicitly named controls with ['control'] format.


Solution

  • The formItem variable that you have is not a FormControl. The solution, then, is to access the controls array on the FormArray directly and iterate:

    for (let formItem of <FormArray>this.myForm.controls['myArray'].controls) {
        if ((<FormGroup>formItem).controls['itemCode'] === resItem.code) {
              <...>
              (<FormGroup>formItem).controls['selected'].setValue(true);
        }
    }
    

    The other way should have a bunch of "undefined" popping up if you log them.

    Here's a plunker demonstrating this subtlety: http://plnkr.co/edit/JRbrPR2g2Kl3z6VJV5jQ?p=preview

    For your TypeScript errors, if you know better than the compiler then you're going to have to cast.

    Here it is for the find version:

    (<FormGroup>control).controls['selected'].setValue(true);