问题
I failed to get an Angular 2 reactive form to work which has a FormGroup nested in a FormArray. Can somebody show me what is wrong in my setup.
Unrelated parts of the code has been omitted for the brevity.
Following is my component
orderForm: FormGroup = this.fb.group({
id: [''],
store: ['', Validators.required],
//The part related to the error
order_lines: this.fb.array([
this.fb.group({
id: [''],
order_id: [],
product_id: [],
description: ['', Validators.required],
unit_price: ['', Validators.required],
discount: [0],
units: [1, Validators.required],
line_total: ['', Validators.required]
})
])
});
constructor(private fb: FormBuilder) { }
//Order instance is passed from elsewhere in the code
select(order: Order): void {
this.orderForm.reset(order)
}
The Order
instance passed to the select method is like this:
{
"id": 20,
"store": "Some Store",
"order_lines": [
{
"id": 6,
"order_id": 20,
"product_id": 1,
"description": "TU001: Polka dots",
"unit_price": "1000.00",
"discount": "100.00",
"units": 2,
"line_total": "1900.00"
},
{
"id": 7,
"order_id": 20,
"product_id": 2,
"description": "TU002: Polka dots",
"unit_price": "500.00",
"discount": "0.00",
"units": 1,
"line_total": "500.00"
}
]
}
The view template is like below.
<form [formGroup]="orderForm">
<input type="number" formControlName="id">
<input type="text" formControlName="store">
<div formArrayName="order_lines">
<div *ngFor="let line of orderForm.get('order_lines'); let i=index">
<div [formGroupName]="i">
<input type="text" [formControlName]="product_id">
<input type="text" [formControlName]="description">
<input type="number" [formControlName]="units">
<input type="number" [formControlName]="unit_price">
<input type="number" [formControlName]="line_total">
</div>
</div>
</div>
</form>
This setup gives me a console error **Cannot find control at order_lines -> 0 -> **. I'm wondering what I'm doing wrong.
I could get this to work with a simple FormControl inside the order_lines FormArray. But it fails with the given error when a FormGroup is used inside the FormArray.
Can you please help me to get this working.
回答1:
@Johna, to complementary my answer:
You have two functions
buildForm(data:any):FormGroup
{
return data?this.fb.group({
id: [data.id?data.id:''],
store: [data.store?data.store:'', Validators.required],
order_lines:this.fb.array(this.buildArrayControl(data.order_lines?data.order_lines:null))
})
:
this.fb.group({
id: [''],
store: ['', Validators.required],
order_lines:this.fb.array(this.buildArrayControl(null))
})
}
buildArrayControl(data:any[]|null):FormGroup[]
{
return data?
data.map(x=>{
return this.fb.group({
id: [x.id?x.id:''],
order_id: [x.order_id?x.order_id:''],
product_id: [x.product_id?x.product_id:''],
description: [x.description?x.description:'', Validators.required],
unit_price: [x.unit_price?x.unit_price:'', Validators.required],
discount: [x.discount?x.discount:0],
units: [x.units?x.units:1, Validators.required],
line_total: [x.line_total?x.line_total:'', Validators.required]
})
})
:
[this.fb.group({
id: [''],
order_id: [],
product_id: [],
description: ['', Validators.required],
unit_price: ['', Validators.required],
discount: [0],
units: [1, Validators.required],
line_total: ['', Validators.required]
})
]
}
then you can do, e.g.
this.orderForm = this.buildForm(
{
id:'112',
store:'22655',
order_lines:[{description:1222,....},{description:1455,...}]
}
)
//or
this.orderForm=this.buildForm(null);
回答2:
Instead of using form.get
better to go with controls like below -
<form [formGroup]="orderForm">
<input type="number" formControlName="id">
<input type="text" formControlName="store">
<div formArrayName="order_lines">
<div *ngFor="let line of orderForm.controls.order_lines.controls; let i=index">
<div [formGroupName]="i">
<input type="text" formControlName="product_id">
<input type="text" formControlName="description">
<input type="number" formControlName="units">
<input type="number" formControlName="unit_price">
<input type="number" formControlName="line_total">
</div>
</div>
</div>
</form>
Working Example
What you are missing -
- you are missing
.control
in last of *ngFor to iterate over the controls. - If you are using above approach you need to replace
[formControlName]
withformControlName
回答3:
You can move formArrayName inline with *ngFor and include .controls in ngFor, as we are looping the form controls.
<div>
<div formArrayName="order_lines" *ngFor="let line of orderForm.get('order_lines').controls; let i=index">
<div [formGroupName]="i">
<input type="text" [formControlName]="product_id">
<input type="text" [formControlName]="description">
<input type="number" [formControlName]="units">
<input type="number" [formControlName]="unit_price">
<input type="number" [formControlName]="line_total">
</div>
</div>
</div>
回答4:
I know OP's question has been already answered, but i'd like to post the solution to my problem, which i solved thanks to OP's question and above answers.
I was asked to create a dynamic form(a questionnaire) but had some problems implementing nested Angular dynamic forms.
The resulting dynamic form can be prepopulated with a number of entries, or you can add more fields to an empty list, or both.
My use case creates a car check-up form and every item on the starting array is a 'TO-DO' task for the operator.
Angular 9 + Material + Bootstrap.
The following code will clarify better:
/* Component Typescript */
constructor( private formBuilder: FormBuilder ) { }
arrayTitoli = [
"TASK1",
"TASK2",
"TASK3",
"TASK4",
"TASK5",
]
addTaskField(value) {
this.array.push(
this.formBuilder.group({
titolo: value,
checkbox: false,
noteAggiuntive: ""
})
)
}
get titoliArray(){
let array = [];
for(let i = 0;i <this.arrayTitoli.length;i++){
array.push(
this.formBuilder.group({
titolo: this.arrayTitoli[i],
checkbox: false,
noteAggiuntive: ""
})
)
}
return array;
}
angForm: FormGroup = this.formBuilder.group({
array: this.formBuilder.array(this.titoliArray)
});
get array(): FormArray {
return this.angForm.get('array') as FormArray;
}
onFormSubmit(): void {
console.log(this.angForm.value)
}
<!-- Component HTML -->
<div class="p-2">
<form [formGroup] = "angForm" (ngSubmit)="onFormSubmit()">
<div>
<button type="submit" class="btn btn-primary">Send</button>
</div>
<div formArrayName="array">
<div *ngFor="let line of array.controls; index as idx;">
<div [formGroupName]="idx">
<mat-grid-list style="text-align: center!important;" [cols]="3" rowHeight="13vh" (window:resize)="onResize($event)">
<mat-grid-tile style="font-weight: bold;font-size: smaller;" class="col-md-4 col-xs-4" colspan="1">
{{line.controls['titolo'].value}}
</mat-grid-tile>
<mat-grid-tile class="col-md-4" colspan="1">
<mat-checkbox formControlName="checkbox" #check color="primary">
<!--<button *ngIf="!mobile" type="button" (click)="check.toggle()" class="btn btn-{{check.checked ? 'success' : 'danger'}} btn-sm" >SPUNTA</button>-->
</mat-checkbox>
<mat-form-field [style.maxWidth.%]="mobile ? '70' : '100'" style="margin-left: 10%;">
<mat-label *ngIf="!mobile">Note aggiuntive</mat-label>
<mat-label *ngIf="mobile">Note</mat-label>
<textarea formControlName="noteAggiuntive" matInput value='' cdkTextareaAutosize #autosize="cdkTextareaAutosize" cdkAutosizeMinRows="1"
cdkAutosizeMaxRows="5"></textarea>
</mat-form-field>
</mat-grid-tile>
</mat-grid-list>
<hr>
</div>
</div>
</div>
</form>
<div>
<mat-form-field>
<input type="text" #newTask matInput>
</mat-form-field>
<span class="px-2">
<button type="button" class="btn btn-primary" (click)="addTaskField(newTask.value)">Create</button>
</span>
</div>
</div>
Hope it can be useful.
来源:https://stackoverflow.com/questions/53774503/formgroup-inside-formarray-in-angular-2-reactive-forms