I am trying to make an interactive form that on each row lists an item together with a remove button (called verwijderen in my example). These items are retrieved from the database and each instantiated as a custom object called LaborPeriod
.
These objects are then transformed into FormGroup
object and then added to a FormArray
. The definition of this formgroup is the following:
let formGroup = this.fb.group({
id: this.account.laborperiods[i].id,
beginDate: this.account.laborperiods[i].beginDate,
endDate: this.account.laborperiods[i].endDate,
hours: this.account.laborperiods[i].hours,
account: this.account.laborperiods[i].account
})
if(!this.laborPeriodArray){
this.laborPeriodArray = new FormArray([])
}
this.laborPeriodArray.push(formGroup)
}
The definition of the laborPeriodArray in the main FormGroup is also laborPeriodArray. The main FormGroup looks like the following:
constructor(private fb: FormBuilder,
private route: ActivatedRoute,
private router: Router,
private accountService: AccountService) {
this.title = "Account aanpassen";
//console.log(this.account.enabled + " : " + this.account.role)
this.accountUpdateForm = this.fb.group({
name: [''],
username: ['', [Validators.required, Validators.email]],
status: '',
permission:'',
laborPeriodArray:this.fb.array([])
});
}
The entire ponent looks like this: Here you can see that laborPeriodArray gets initialized in the onInit method.
constructor(private fb: FormBuilder,
private route: ActivatedRoute,
private router: Router,
private accountService: AccountService) {
this.title = "Account aanpassen";
//console.log(this.account.enabled + " : " + this.account.role)
this.accountUpdateForm = this.fb.group({
name: [''],
username: ['', [Validators.required, Validators.email]],
status: '',
permission:'',
laborPeriodArray:this.fb.array([])
});
}
ngOnInit(): void {
let formGroupArray: FormGroup[] = [];
this.route.paramMap
.switchMap((params: ParamMap) =>
this.accountService.getAccount(params.get("id")))
.subscribe(account => {
this.account = account;
for(let i=0; i < account.laborperiods.length; i++){
let formGroup = this.fb.group({
id: this.account.laborperiods[i].id,
beginDate: this.account.laborperiods[i].beginDate,
endDate: this.account.laborperiods[i].endDate,
hours: this.account.laborperiods[i].hours,
account: this.account.laborperiods[i].account
})
if(!this.laborPeriodArray){
this.laborPeriodArray = new FormArray([])
}
this.laborPeriodArray.push(formGroup)
}
console.log("laborPeriod" + JSON.stringify(this.laborPeriodArray.length))
this.ngOnChanges();
});
}
ngOnChanges(): void {
if (this.account !== undefined) {
this.accountUpdateForm.reset({
name: this.account.name,
username: this.account.username,
status: this.account.enabled,
permission: this.account.admin,
laborPeriodArray: this.laborPeriodArray
});
}
}
All the FormGroup items are added but not displayed. The rows are blank. This is the relevant snippet from my HTML page
<table class="table table-hover">
<thead>
<tr>
<th>Begin datum</th>
<th>Eind datum</th>
<th>Aantal uren</th>
<th>Actie</th>
</tr>
</thead>
<tbody>
<tr formArrayName="laborPeriodArray" *ngFor = "let laborperiod of laborPeriodArray.controls; let i = index" [formGroupName]="i">
<td formControlName="beginDate">{{laborperiod.value.get('beginDate') | date:'yyyy-MM-dd'}}</td>
<td formControlName="endDate">{{laborperiod.value.get('endDate') | date:'yyyy-MM-dd'}}</td>
<td formControlName="hours">{{laborperiod.value.get('hours')}}</td>
<button type="button" class="btn btn-default">
<span class="glyphicon glyphicon-remove" aria-hidden="true"></span>
Verwijderen
</button>
</tr>
This is giving me the browser error ERROR Error: Cannot find control with unspecified name attribute
but I am specifying the formControlName for each td row. So I don't understand what is causing the error. Furthermore I would also like to know how I can link the Remove button to the data corresponding to each row. I take it I have to use the index I for this but i'm not sure.
EDIT:
After applying AJT_82's solution I still don't have it working. It appears to have something to do with the database retrieved data itself. When I add the example account array of AJT_82 to my ngOnInit() like so:
account1 = { laborperiods: [{ id: 1, hours:11 }, { id: 2, hours:22 }, { id: 3, hours:33 }] }
ngOnInit(): void {
this.route.paramMap
.switchMap((params: ParamMap) =>
this.accountService.getAccount(params.get("id")))
.subscribe(account => {
this.account = account;
for(let i=0; i < account.laborperiods.length; i++){
let formGroup = this.fb.group({
id: this.account1.laborperiods[i].id,
beginDate: this.account.laborperiods[i].beginDate,
endDate: this.account.laborperiods[i].endDate,
hours: this.account1.laborperiods[i].hours,
account: this.account.laborperiods[i].account
})
this.laborPeriodArray.push(formGroup)
}
console.log(JSON.stringify(account.laborperiods[0].beginDate))
this.ngOnChanges();
});
}
it works but it shows only 3 rows. That's the total length of the example account array.
This is the account class:
export class Account {
id: number;
username: string;
name: string;
enabled: boolean
password: string;
person: Person;
admin: boolean;
laborperiods: LaborPeriod[]
remainingLeaveHours:number;
}
and this is the LaborPeriod class:
export class LaborPeriod{
id: number
beginDate: Date
endDate: Date
hours: number
account: Account
}
Is there anything wrong with its field declarations?
I am trying to make an interactive form that on each row lists an item together with a remove button (called verwijderen in my example). These items are retrieved from the database and each instantiated as a custom object called LaborPeriod
.
These objects are then transformed into FormGroup
object and then added to a FormArray
. The definition of this formgroup is the following:
let formGroup = this.fb.group({
id: this.account.laborperiods[i].id,
beginDate: this.account.laborperiods[i].beginDate,
endDate: this.account.laborperiods[i].endDate,
hours: this.account.laborperiods[i].hours,
account: this.account.laborperiods[i].account
})
if(!this.laborPeriodArray){
this.laborPeriodArray = new FormArray([])
}
this.laborPeriodArray.push(formGroup)
}
The definition of the laborPeriodArray in the main FormGroup is also laborPeriodArray. The main FormGroup looks like the following:
constructor(private fb: FormBuilder,
private route: ActivatedRoute,
private router: Router,
private accountService: AccountService) {
this.title = "Account aanpassen";
//console.log(this.account.enabled + " : " + this.account.role)
this.accountUpdateForm = this.fb.group({
name: [''],
username: ['', [Validators.required, Validators.email]],
status: '',
permission:'',
laborPeriodArray:this.fb.array([])
});
}
The entire ponent looks like this: Here you can see that laborPeriodArray gets initialized in the onInit method.
constructor(private fb: FormBuilder,
private route: ActivatedRoute,
private router: Router,
private accountService: AccountService) {
this.title = "Account aanpassen";
//console.log(this.account.enabled + " : " + this.account.role)
this.accountUpdateForm = this.fb.group({
name: [''],
username: ['', [Validators.required, Validators.email]],
status: '',
permission:'',
laborPeriodArray:this.fb.array([])
});
}
ngOnInit(): void {
let formGroupArray: FormGroup[] = [];
this.route.paramMap
.switchMap((params: ParamMap) =>
this.accountService.getAccount(params.get("id")))
.subscribe(account => {
this.account = account;
for(let i=0; i < account.laborperiods.length; i++){
let formGroup = this.fb.group({
id: this.account.laborperiods[i].id,
beginDate: this.account.laborperiods[i].beginDate,
endDate: this.account.laborperiods[i].endDate,
hours: this.account.laborperiods[i].hours,
account: this.account.laborperiods[i].account
})
if(!this.laborPeriodArray){
this.laborPeriodArray = new FormArray([])
}
this.laborPeriodArray.push(formGroup)
}
console.log("laborPeriod" + JSON.stringify(this.laborPeriodArray.length))
this.ngOnChanges();
});
}
ngOnChanges(): void {
if (this.account !== undefined) {
this.accountUpdateForm.reset({
name: this.account.name,
username: this.account.username,
status: this.account.enabled,
permission: this.account.admin,
laborPeriodArray: this.laborPeriodArray
});
}
}
All the FormGroup items are added but not displayed. The rows are blank. This is the relevant snippet from my HTML page
<table class="table table-hover">
<thead>
<tr>
<th>Begin datum</th>
<th>Eind datum</th>
<th>Aantal uren</th>
<th>Actie</th>
</tr>
</thead>
<tbody>
<tr formArrayName="laborPeriodArray" *ngFor = "let laborperiod of laborPeriodArray.controls; let i = index" [formGroupName]="i">
<td formControlName="beginDate">{{laborperiod.value.get('beginDate') | date:'yyyy-MM-dd'}}</td>
<td formControlName="endDate">{{laborperiod.value.get('endDate') | date:'yyyy-MM-dd'}}</td>
<td formControlName="hours">{{laborperiod.value.get('hours')}}</td>
<button type="button" class="btn btn-default">
<span class="glyphicon glyphicon-remove" aria-hidden="true"></span>
Verwijderen
</button>
</tr>
This is giving me the browser error ERROR Error: Cannot find control with unspecified name attribute
but I am specifying the formControlName for each td row. So I don't understand what is causing the error. Furthermore I would also like to know how I can link the Remove button to the data corresponding to each row. I take it I have to use the index I for this but i'm not sure.
EDIT:
After applying AJT_82's solution I still don't have it working. It appears to have something to do with the database retrieved data itself. When I add the example account array of AJT_82 to my ngOnInit() like so:
account1 = { laborperiods: [{ id: 1, hours:11 }, { id: 2, hours:22 }, { id: 3, hours:33 }] }
ngOnInit(): void {
this.route.paramMap
.switchMap((params: ParamMap) =>
this.accountService.getAccount(params.get("id")))
.subscribe(account => {
this.account = account;
for(let i=0; i < account.laborperiods.length; i++){
let formGroup = this.fb.group({
id: this.account1.laborperiods[i].id,
beginDate: this.account.laborperiods[i].beginDate,
endDate: this.account.laborperiods[i].endDate,
hours: this.account1.laborperiods[i].hours,
account: this.account.laborperiods[i].account
})
this.laborPeriodArray.push(formGroup)
}
console.log(JSON.stringify(account.laborperiods[0].beginDate))
this.ngOnChanges();
});
}
it works but it shows only 3 rows. That's the total length of the example account array.
This is the account class:
export class Account {
id: number;
username: string;
name: string;
enabled: boolean
password: string;
person: Person;
admin: boolean;
laborperiods: LaborPeriod[]
remainingLeaveHours:number;
}
and this is the LaborPeriod class:
export class LaborPeriod{
id: number
beginDate: Date
endDate: Date
hours: number
account: Account
}
Is there anything wrong with its field declarations?
Share Improve this question edited Nov 17, 2017 at 9:10 Maurice asked Nov 16, 2017 at 11:11 MauriceMaurice 7,41111 gold badges60 silver badges124 bronze badges2 Answers
Reset to default 7You cannot have formArrayName
on the same element as your iteration and formGroupName
, you need to move the formArrayName
to an upper level. Also I see no use of the formControlName
, as these are not editable fields, and Angular will throw error for this. Also the use of for example...
{{laborperiod.value.get('beginDate') | date:'yyyy-MM-dd'}}
is incorrect, it should be just
{{laborperiod.value.beginDate | date:'yyyy-MM-dd'}}
Assumingly in your code, the variable laberPeriodArray
is declared...
this.laborPeriodArray =
this.accountUpdateForm.controls.laborPeriodArray as FormArray
since you are referring to this.laborPeriodArray
in your code, therefore the following:
if(!this.laborPeriodArray){
this.laborPeriodArray = new FormArray([])
}
is redundant, you have already declared it in the build of the form as an empty FormArray. But that is just a sidenote :)
All in all, your template should look something like this:
<table formArrayName="laborPeriodArray" >
<tr *ngFor="let laborperiod of laborPeriodArray.controls;
let i = index" [formGroupName]="i">
<td>{{laborperiod.value.hours}}</td>
</tr>
</table>
DEMO
Try replacing
laborperiod.value.get('beginDate')
By
laborperiod.value['beginDate']
I also remande that you FormGroup variable to be a Class attribute and not a OnInit() one.
I also remande using ReactiveForms with it's API FormControls.
- Let me know what happened