Sumit Agarwal
Sumit Agarwal

Reputation: 4306

Angular 2: Accessing data from FormArray

I have prepared a from using ReactiveForms provided by angular2/forms. This form has a form array products:

this.checkoutFormGroup = this.fb.group({
            selectedNominee: ['', Validators.required],
            selectedBank: ['', Validators.required],
            products: productFormGroupArray
        });

productFormGroupArray is a array of FormGroup Objects.I fetched the controls i.e. FormArray object using this:

this.checkoutFormGroup.get('products')

I am trying to get the element in the products array at index i. How can this be done without looping through the array?

Edit:

I tried with at(index) method available:

this.checkoutFormGroup.get('products').at(index)

but this is generating an error as:

Property 'at' does not exist on type 'AbstractControl'.

Edit 2: checkoutData and fund are received from server.

this.checkoutData.products.forEach(product => {
                    this.fundFormGroupArray.push(this.fb.group({
                        investmentAmount: [this.fund.minInvestment, Validators.required],
                        selectedSubOption: ['', Validators.required],
                    }))
            });

Upvotes: 65

Views: 143180

Answers (6)

Typhon
Typhon

Reputation: 1056

Angular 14

Angular users rejoice, since Angular v14, you can type FormGroup, FormArray & FormControl, which means you no longer have to cast the AbstractControls.

With the given code:

const formGroup = new FormGroup({
  list: new FormArray([
    new FormControl('first'),
    new FormControl('second'),
  ]),
});

You can now directly do:

const firstValue = formGroup.controls.list.at(0); // string | null
const secondValue = formGroup.controls.list.at(1); // string | null

If you want a stricter typing which exclude the null value, you can create your FormControl with the nonNullable option set to true:

const control = new FormControl('value', {nonNullable: true});
const value = control.value; // string

Angular 13,12,11, & earlier (exclude 1)

While casting the AbstractControl to a FormArray before using the at() method is a way of doing it, I haven't seen anybody pointing out that you can also do it using the get() method, which requires no casting.

According to Angular's Documentation, the signature of get() is:
get(path: string | (string | number)[]): AbstractControl | null

Which means you can also access FormArray's controls with it.

Example:

const formGroup = new FormGroup({
  list: new FormArray([
    new FormControl('first'),
    new FormControl('second'),
  ]),
});

const firstValue = formGroup.get('list.0').value; // Returns 'first'
const secondValue = formGroup.get('list.1').value; // Returns 'second'

This is really useful, when you want to bind a FormControl in the HTML, where you can't cast anything:

<input [formControl]="formGroup.get('list.0')">

Here is a summary of ways of doing it:

const firstControl = listControl.get('list.0');
const firstControl = listControl.get(['list', 0]);
const firstControl = listControl.get('list').get('0'); // You need a string and not a number
const listControl = formGroup.get('list') as FormArray;
const firstControl = listControl.at(0);

Upvotes: 30

Stokely
Stokely

Reputation: 15799

Angular : Accessing FormArray with FormGroup Children

Assuming this is your Angular Form design and you just want "products" (your FormArray)...

this.checkoutFormGroup = this.fb.group({
            selectedNominee: ['', Validators.required],
            selectedBank: ['', Validators.required],
            products: new FormArray([
              new FormGroup({
                id: new FormControl('hello'),
                name: new FormControl('world')
              }),
              new FormGroup({
                id: new FormControl('its'),
                name: new FormControl('me')
              }),
          ])
        });

You can get a reference to the FormArray this way...

let formArrayObject = (<FormArray>this.checkoutFormGroup.get('products'));

...get the FormArray's first FormGroup child this way...

let firstFormArrayFormGroup = (<FormArray>this.checkoutFormGroup.get('products'))?.controls[0] as FormGroup;

...and get the FormArray's first and second FormGroup's FormControls this way...

let formArrayFormGroupFormControl1 = (<FormGroup>(<FormArray>this.checkoutFormGroup.get('products'))?.controls[0])?.controls["message1"].value;
let formArrayFormGroupFormControl2 = (<FormGroup>(<FormArray>this.checkoutFormGroup.get('products'))?.controls[0])?.controls["message2"].value;
let formArrayFormGroupFormControl3 = (<FormGroup>(<FormArray>this.checkoutFormGroup.get('products'))?.controls[1])?.controls["message1"].value;
let formArrayFormGroupFormControl4 = (<FormGroup>(<FormArray>this.checkoutFormGroup.get('products'))?.controls[1])?.controls["message2"].value;

// returns: "hello"
// returns: "world"
// returns: "its"
// returns: "me"

Note: Each of the code blocks above use an extra cast call as <FormGroup> as each code returns an "AbstractControl". FormGroup, FormControl, etc are subtypes of the AbstractControl type, so you have to tell Angular what subtype is expected using the cast so you can instantly access their properties.

Like I said, unlike the other posts, these are ideal for getting your form controls dynamically like in a large table of angular data. All you need is the index of your form group and the names of your form controls.

Not sure who designed this system at Angular, as it is not intuitive. This is why Angular has such a steep learning curve.

But there you go...

Upvotes: 0

Tadele Ayelegn
Tadele Ayelegn

Reputation: 4706

Inside the component: use this

(<FormArray>this.checkoutFormGroup.get('products')).at(index).get('yourFormControlName')

And inside DOM(to apply validation on the formcontrol) use it this way:

<form (ngSubmit)="onSubmit()" [formGroup]="checkoutFormGroup">
   
      <div>
        <h4 >Add Products Below</h4>
      
        <div  formArrayName="products">
          
          <div *ngFor="let subFormGroup of getControls(); let i = index">
            <div class="expense__input" [formGroupName]="i">
              <div class="input__group">
                <label for="Expense Type">Expense Type</label>
                <input type="text" 
                formControlName="productType">
                <span *ngIf="this.expenseForm.get('products').at(i).get('products').invalid">Product Type is Required</span>
              </div>
           </div>
        </div>
      </div>
   </div>

Upvotes: 0

fg78nc
fg78nc

Reputation: 5232

One liner as an option to the current accepted answer

var item = (<FormArray>this.checkoutFormGroup.get('products')).at(index);

Upvotes: 13

Issa Lafi
Issa Lafi

Reputation: 355

// in .ts component file //

getName(i) {
    return this.getControls()[i].value.name;
  }

  getControls() {
    return (<FormArray>this.categoryForm.get('categories')).controls;
  }

// in reactive form - Template file //

<mat-tab-group formArrayName="categories" class="uk-width-2-3" [selectedIndex]="getControls().length">
      <mat-tab
        *ngFor="let categoryCtrl of getControls(); let i = index"
        [formGroupName]="i"
        [label]="getName(i)? getName(i) : 'جديد'"
      >
</mat-tab>
</mat-tab-group>

Upvotes: 3

Radim K&#246;hler
Radim K&#246;hler

Reputation: 123861

Just cast that control to array

var arrayControl = this.checkoutFormGroup.get('products') as FormArray;

and all its features are there

var item = arrayControl.at(index);

Upvotes: 101

Related Questions