Reputation: 4306
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
Reputation: 1056
Angular users rejoice, since Angular v14, you can type FormGroup
, FormArray
& FormControl
, which means you no longer have to cast the AbstractControl
s.
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
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
Reputation: 15799
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
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
Reputation: 5232
One liner as an option to the current accepted answer
var item = (<FormArray>this.checkoutFormGroup.get('products')).at(index);
Upvotes: 13
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
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