19

Unable to patch values to FormArray resultList.

Anybody can please explain me, what i'm missing?

TS File:

import { Component, OnInit } from '@angular/core';
import { Student } from '../student';
import { FormGroup, FormControl, Validators, FormArray } from '@angular/forms';

@Component({
  selector: 'app-container',
  templateUrl: './container.component.html',
  styleUrls: ['./container.component.css']
})

export class ContainerComponent implements OnInit {

  studList: Student[] = [];
  myform: FormGroup = new FormGroup({
    firstName: new FormControl('', [Validators.required, Validators.minLength(4)]),
    lastName: new FormControl(),
    gender: new FormControl('male'),
    dob: new FormControl(),
    qualification: new FormControl(),
    resultList: new FormArray([])
  });    

  onSave() {
    let stud: Student = new Student();
    stud.firstName = this.myform.get('firstName').value;
    stud.lastName = this.myform.get('lastName').value;
    stud.gender = this.myform.get('gender').value;
    stud.dob = this.myform.get('dob').value;
    stud.qualification = this.myform.get('qualification').value;
    this.studList.push(stud);
    this.myform.controls.resultList.patchValue(this.studList);
    console.log(JSON.stringify(this.studList));
  }

  ngOnInit() {
  }
}

Model:

export class Student {
    public firstName: String;
    public lastName: string;
    public gender: string;
    public dob: string;
    public qualification: string;
}

HTML:

    <div class="container">
        <h3>Striped Rows</h3>
        <table class="table table-striped" formArrayName="resultList">
            <thead>
                <tr>
                    <th>Firstname</th>
                </tr>
            </thead>
            <tbody>
                <tr *ngFor="let item of myform.controls.resultList.controls; let i = index" [formGroupName]="i">
                    <td><p formControlName="firstName"></p></td>
                </tr>
            </tbody>
        </table>
    </div>

this.studList JSON:

[  
   {  
      "firstName":"santosh",
      "lastName":"jadi",
      "gender":"male",
      "dob":"2018-03-31T18:30:00.000Z",
      "qualification":"BE"
   },
   {  
      "firstName":"santosh",
      "lastName":"jadi",
      "gender":"male",
      "dob":"2018-03-31T18:30:00.000Z",
      "qualification":"BE"
   }
]
Santosh Jadi
  • 1,479
  • 6
  • 29
  • 55

7 Answers7

25

By your question you want to add new Student to resultList. First of all, you need to know FormArray is an array of AbstractControl. You can add to array only type of AbstractControl not other. To simplify task prefer to use FormBuilder:

 constructor(private fb: FormBuilder) {}

  createForm() {

    this.myform = this.fb.group({
      firstName: ['', [Validators.required, Validators.minLength(4)]],
      lastName: [],
      gender: ['male'],
      dob: [],
      qualification: [],
      resultList: new FormArray([])
    });
  }

As you can see before filling resultList FormArray, it's mapped to FormGroup:

onSave() {
    let stud: Student = new Student();
    stud.firstName = 'Hello';
    stud.lastName = 'World';
    stud.qualification = 'SD';
    this.studList.push(stud);

    let studFg = this.fb.group({
      firstName: [stud.firstName, [Validators.required, Validators.minLength(4)]],
      lastName: [stud.lastName],
      gender: [stud.gender],
      dob: [stud.dob],
      qualification: [stud.qualification],
    })
     let formArray = this.myform.controls['resultList'] as FormArray;
    formArray.push(studFg);
    console.log(formArray.value)
  }

FormBuilder - Creates an AbstractControl from a user-specified configuration.

It is essentially syntactic sugar that shortens the new FormGroup(), new FormControl(), and new FormArray() boilerplate that can build up in larger forms.

Also, in html formControlName bound to <p> element, It's not an input and you can't bind to not form elements like div/p/span...:

 <tbody>
                <tr *ngFor="let item of myform.controls.resultList.controls; let i = index" [formGroupName]="i">
                    <td><p formControlName="firstName"></p></td> <==== Wrong element 
                </tr>
</tbody>

So, I think you just want to show added students in table. Then iterate over studList and show it's value in table:

<tbody>
                <tr *ngFor="let item of studList; let i = index" [formGroupName]=i>
                    <td>
                        <p> {{item.firstName}} </p>
                    </td>
                </tr>
</tbody>

Patching value

Take care when patching array. Because patchValue of FormArray patches values by the index:

 patchValue(value: any[], options: {onlySelf?: boolean, emitEvent?: boolean} = {}): void {
    value.forEach((newValue: any, index: number) => {
      if (this.at(index)) {
        this.at(index).patchValue(newValue, {onlySelf: true, emitEvent: options.emitEvent});
      }
    });
    this.updateValueAndValidity(options);
  }

So, code below it patches the element at index=0: First indexed value of this.myform.controls['resultList'] as FormArray will be replaced with:

let stud1 = new Student();

stud1.firstName = 'FirstName';
stud1.lastName = 'LastName';
stud1.qualification = 'FFF';
formArray.patchValue([stud1]);

Your case doesn't work because patchValue requires some controls in array. In your case there is no controls in array. Look source code.

StackBlitz Demo

Yerkon
  • 4,548
  • 1
  • 18
  • 30
  • Good example. This should work. If you have time look at: https://stackoverflow.com/questions/49666593/add-item-in-dynamic-reactive-form-in-angular/49667898?noredirect=1#comment86347676_49667898 – Swoox Apr 17 '18 at 14:11
6

First try with this steps and make sure are you on correct way

Because in your scenario you are patching the object to formArray ,so you have to parse that object first & check once have you imported ReactiveFormsModule in your app.module.ts.

Omkar Jadhav
  • 656
  • 1
  • 5
  • 18
4

you have to co like this , code is taken from angular.io, you need to do setcontrol that will do or go though link there is code for the same it makes uses of Address array

 this.setAddresses(this.hero.addresses);

  setAddresses(addresses: Address[]) {
    const addressFGs = addresses.map(address => this.fb.group(address));
    const addressFormArray = this.fb.array(addressFGs);
    this.heroForm.setControl('secretLairs', addressFormArray);
  }
Pranay Rana
  • 175,020
  • 35
  • 237
  • 263
2

I'd prefer using FormBuilder to create form.

export class ComponentName implements OnInit {
    form: FormGroup;
    constructor(private fb: FormBuilder){}

    ngOnInit() {
       this.buildForm();
    }

    buildForm() {
        this.form = this.fb.group({
            firstName: '',
            lastName: '',
            ...
            resultList: this.fb.array([])
        });
    }
}

I believe the studlist will be obtained through API call as an observable rather than an static array. Let's assume, we data as follow.

resultModel = 
{
    firstName: "John",
    lastName: "Doe",
    ....
    resultList: [
       {
            prop1: value1,
            prop2: value2,
            prop3: value3
       },
       {
            prop1: value1,
            prop2: value2,
            prop3: value3
       }
       ...
    ]
}

Once the data is available, we can patch the values as follow:

patchForm(): void {
        this.form.patchValue({
            firstName: this.model.firstName,
            lastName: this.model.lastName,
            ...
        });

        // Provided the FormControlName and Object Property are same
        // All the FormControls can be patched using JS spread operator as 

        this.form.patchValue({
            ...this.model
        });

        // The FormArray can be patched right here, I prefer to do in a separate method
        this.patchResultList();
}

// this method patches FormArray
patchResultList() {
    let control = this.form.get('resultList') as FormArray;
    // Following is also correct
    // let control = <FormArray>this.form.controls['resultList'];

   this.resultModel.resultList.forEach(x=>{
        control.push(this.fb.group({
            prop1: x.prop1,
            prop2: x.prop2,
            prop3: x.prop3,

        }));
    });
}
20B2
  • 2,011
  • 17
  • 30
1

Array does not contain patchValue method. You have to iterate over controls and patchValue each of them separately.

Roberto Zvjerković
  • 9,657
  • 4
  • 26
  • 47
  • can you explain with sample example – Santosh Jadi Apr 08 '18 at 08:40
  • 2
    @ritaj, wrong. FormArray has method `patchValue` [example](https://angular.io/api/forms/FormArray#example-2) and [source code](https://github.com/angular/angular/blob/5298b2bda34a8766b28c8425e447f94598b23901/packages/forms/src/model.ts#L1402-L1409) – Yerkon Apr 14 '18 at 16:02
0

I am using the formgroup in formarray as:

this.formGroup = new FormGroup({
      clientCode: new FormControl('', []),
      clientName: new FormControl('', [Validators.required, Validators.pattern(/^[a-zA-Z0-9 _-]{0,50}$/)]),
      type: new FormControl('', [Validators.required]),
      description: new FormControl('', []),
      industry: new FormControl('', []),
      website: new FormControl('', [Validators.required, Validators.pattern(this.settings.regex.website)]),
      businessEmail: new FormControl('', [Validators.pattern(this.settings.regex.email)]),
      clients: this._formBuilder.array([this._formBuilder.group({
        contactPerson: new FormControl('', [Validators.required]),
        contactTitle: new FormControl('', [Validators.required]),
        phoneNumber: new FormControl('', [Validators.required, Validators.pattern(this.settings.regex.phone)]),
        emailId: new FormControl('', [Validators.required, Validators.pattern(this.settings.regex.email)]),
        timeZone: new FormControl('', [Validators.required, Validators.pattern(this.settings.zipCode), Validators.minLength(5), Validators.maxLength(12)])
      })])
    })

For patch value I am using below method as:

let control = _this.formGroup.get('clients') as FormArray
        clients.forEach(ele => {
          control.push(_this._formBuilder.group({
            contactPerson: new FormControl(ele.client_name, [Validators.required]),
            contactTitle: new FormControl(ele.contact_title, [Validators.required]),
            phoneNumber: new FormControl(ele.phone_number, [Validators.required, Validators.pattern(_this.settings.regex.phone)]),
            emailId: new FormControl(ele.email_id, [Validators.required, Validators.pattern(_this.settings.regex.email)]),
            timeZone: new FormControl(ele.timezone, [Validators.required, Validators.pattern(_this.settings.zipCode), Validators.minLength(5), Validators.maxLength(12)])
          }))
        });

Using this method we can validate the nested field as well.

Hope this may help.

0

First create your form based on your retrieved data structure then use patchValue;

the form needs to contain all form controls before applying patchValue.

let's say your data has a property containing an array of string you do

pseudo-code:

for ( const item of data.myArray ) {

  form.controls.myArray.push( new FormControl('') )

}

then

form.patchValue(data);

this example doesn't make mush sense as you could directly initialize the data inside the for loop but for more complex forms this will make you avoid parsing through all keys an data types to initialize the values.

JSmith
  • 4,519
  • 4
  • 29
  • 45