65

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],
                    }))
            });
2
  • Please share the code which show how you have defined productFormGroupArray Commented Nov 17, 2016 at 5:25
  • @ranakrunal9 Edited the question Commented Nov 17, 2016 at 5:44

6 Answers 6

101

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);
Sign up to request clarification or add additional context in comments.

2 Comments

That's return undefined
How to do this in template / html file??
30

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);

3 Comments

Excellent, thank you for your answer. The get('list.0') instead of the more intuitive get('list[0]') will be a big gotcha for many developers.
How to get the value from the HTML file??
I'm so rejoiced to use Angular now.
13

One liner as an option to the current accepted answer

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

1 Comment

old syntax use as instead
3

// 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>

Comments

0

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>

1 Comment

error TS2339: Property 'at' does not exist on type 'AbstractControl<any, any>'.
0

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...

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.