Unable to patch values to FormArray resultList.
Anybody can please explain me, what i'm missing?
TS File:
import { Component, OnInit } from '#angular/core';
import { Student } from '../student';
import { FormGroup, FormControl, Validators, FormArray } from '#angular/forms';
#Component({
selector: 'app-container',
templateUrl: './container.component.html',
styleUrls: ['./container.component.css']
})
export class ContainerComponent implements OnInit {
studList: Student[] = [];
myform: FormGroup = new FormGroup({
firstName: new FormControl('', [Validators.required, Validators.minLength(4)]),
lastName: new FormControl(),
gender: new FormControl('male'),
dob: new FormControl(),
qualification: new FormControl(),
resultList: new FormArray([])
});
onSave() {
let stud: Student = new Student();
stud.firstName = this.myform.get('firstName').value;
stud.lastName = this.myform.get('lastName').value;
stud.gender = this.myform.get('gender').value;
stud.dob = this.myform.get('dob').value;
stud.qualification = this.myform.get('qualification').value;
this.studList.push(stud);
this.myform.controls.resultList.patchValue(this.studList);
console.log(JSON.stringify(this.studList));
}
ngOnInit() {
}
}
Model:
export class Student {
public firstName: String;
public lastName: string;
public gender: string;
public dob: string;
public qualification: string;
}
HTML:
<div class="container">
<h3>Striped Rows</h3>
<table class="table table-striped" formArrayName="resultList">
<thead>
<tr>
<th>Firstname</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let item of myform.controls.resultList.controls; let i = index" [formGroupName]="i">
<td><p formControlName="firstName"></p></td>
</tr>
</tbody>
</table>
</div>
this.studList JSON:
[
{
"firstName":"santosh",
"lastName":"jadi",
"gender":"male",
"dob":"2018-03-31T18:30:00.000Z",
"qualification":"BE"
},
{
"firstName":"santosh",
"lastName":"jadi",
"gender":"male",
"dob":"2018-03-31T18:30:00.000Z",
"qualification":"BE"
}
]
By your question you want to add new Student to resultList.
First of all, you need to know FormArray is an array of AbstractControl.
You can add to array only type of AbstractControl not other.
To simplify task prefer to use FormBuilder:
constructor(private fb: FormBuilder) {}
createForm() {
this.myform = this.fb.group({
firstName: ['', [Validators.required, Validators.minLength(4)]],
lastName: [],
gender: ['male'],
dob: [],
qualification: [],
resultList: new FormArray([])
});
}
As you can see before filling resultList FormArray, it's mapped to FormGroup:
onSave() {
let stud: Student = new Student();
stud.firstName = 'Hello';
stud.lastName = 'World';
stud.qualification = 'SD';
this.studList.push(stud);
let studFg = this.fb.group({
firstName: [stud.firstName, [Validators.required, Validators.minLength(4)]],
lastName: [stud.lastName],
gender: [stud.gender],
dob: [stud.dob],
qualification: [stud.qualification],
})
let formArray = this.myform.controls['resultList'] as FormArray;
formArray.push(studFg);
console.log(formArray.value)
}
FormBuilder - Creates an AbstractControl from a user-specified
configuration.
It is essentially syntactic sugar that shortens the new FormGroup(),
new FormControl(), and new FormArray() boilerplate that can build up
in larger forms.
Also, in html formControlName bound to <p> element, It's not an input and you can't bind to not form elements like div/p/span...:
<tbody>
<tr *ngFor="let item of myform.controls.resultList.controls; let i = index" [formGroupName]="i">
<td><p formControlName="firstName"></p></td> <==== Wrong element
</tr>
</tbody>
So, I think you just want to show added students in table. Then iterate over studList and show it's value in table:
<tbody>
<tr *ngFor="let item of studList; let i = index" [formGroupName]=i>
<td>
<p> {{item.firstName}} </p>
</td>
</tr>
</tbody>
Patching value
Take care when patching array. Because patchValue of FormArray patches values by the index:
patchValue(value: any[], options: {onlySelf?: boolean, emitEvent?: boolean} = {}): void {
value.forEach((newValue: any, index: number) => {
if (this.at(index)) {
this.at(index).patchValue(newValue, {onlySelf: true, emitEvent: options.emitEvent});
}
});
this.updateValueAndValidity(options);
}
So, code below it patches the element at index=0: First indexed value of this.myform.controls['resultList'] as FormArray will be replaced with:
let stud1 = new Student();
stud1.firstName = 'FirstName';
stud1.lastName = 'LastName';
stud1.qualification = 'FFF';
formArray.patchValue([stud1]);
Your case doesn't work because patchValue requires some controls in array. In your case there is no controls in array. Look source code.
StackBlitz Demo
First try with this steps and make sure are you on correct way
Because in your scenario you are patching the object to formArray ,so you have to parse that object first & check once have you imported ReactiveFormsModule in your app.module.ts.
you have to co like this , code is taken from angular.io, you need to do setcontrol that will do or go though link there is code for the same it makes uses of Address array
this.setAddresses(this.hero.addresses);
setAddresses(addresses: Address[]) {
const addressFGs = addresses.map(address => this.fb.group(address));
const addressFormArray = this.fb.array(addressFGs);
this.heroForm.setControl('secretLairs', addressFormArray);
}
I'd prefer using FormBuilder to create form.
export class ComponentName implements OnInit {
form: FormGroup;
constructor(private fb: FormBuilder){}
ngOnInit() {
this.buildForm();
}
buildForm() {
this.form = this.fb.group({
firstName: '',
lastName: '',
...
resultList: this.fb.array([])
});
}
}
I believe the studlist will be obtained through API call as an observable rather than an static array. Let's assume, we data as follow.
resultModel =
{
firstName: "John",
lastName: "Doe",
....
resultList: [
{
prop1: value1,
prop2: value2,
prop3: value3
},
{
prop1: value1,
prop2: value2,
prop3: value3
}
...
]
}
Once the data is available, we can patch the values as follow:
patchForm(): void {
this.form.patchValue({
firstName: this.model.firstName,
lastName: this.model.lastName,
...
});
// Provided the FormControlName and Object Property are same
// All the FormControls can be patched using JS spread operator as
this.form.patchValue({
...this.model
});
// The FormArray can be patched right here, I prefer to do in a separate method
this.patchResultList();
}
// this method patches FormArray
patchResultList() {
let control = this.form.get('resultList') as FormArray;
// Following is also correct
// let control = <FormArray>this.form.controls['resultList'];
this.resultModel.resultList.forEach(x=>{
control.push(this.fb.group({
prop1: x.prop1,
prop2: x.prop2,
prop3: x.prop3,
}));
});
}
Array does not contain patchValue method. You have to iterate over controls and patchValue
each of them separately.
I am using the formgroup in formarray as:
this.formGroup = new FormGroup({
clientCode: new FormControl('', []),
clientName: new FormControl('', [Validators.required, Validators.pattern(/^[a-zA-Z0-9 _-]{0,50}$/)]),
type: new FormControl('', [Validators.required]),
description: new FormControl('', []),
industry: new FormControl('', []),
website: new FormControl('', [Validators.required, Validators.pattern(this.settings.regex.website)]),
businessEmail: new FormControl('', [Validators.pattern(this.settings.regex.email)]),
clients: this._formBuilder.array([this._formBuilder.group({
contactPerson: new FormControl('', [Validators.required]),
contactTitle: new FormControl('', [Validators.required]),
phoneNumber: new FormControl('', [Validators.required, Validators.pattern(this.settings.regex.phone)]),
emailId: new FormControl('', [Validators.required, Validators.pattern(this.settings.regex.email)]),
timeZone: new FormControl('', [Validators.required, Validators.pattern(this.settings.zipCode), Validators.minLength(5), Validators.maxLength(12)])
})])
})
For patch value I am using below method as:
let control = _this.formGroup.get('clients') as FormArray
clients.forEach(ele => {
control.push(_this._formBuilder.group({
contactPerson: new FormControl(ele.client_name, [Validators.required]),
contactTitle: new FormControl(ele.contact_title, [Validators.required]),
phoneNumber: new FormControl(ele.phone_number, [Validators.required, Validators.pattern(_this.settings.regex.phone)]),
emailId: new FormControl(ele.email_id, [Validators.required, Validators.pattern(_this.settings.regex.email)]),
timeZone: new FormControl(ele.timezone, [Validators.required, Validators.pattern(_this.settings.zipCode), Validators.minLength(5), Validators.maxLength(12)])
}))
});
Using this method we can validate the nested field as well.
Hope this may help.
Related
Hi I am trying to set/ take away custom validators for different elements in a form array that can change around, so far what I have tried to do is create a switch statement and loop through all of the input types that are set so I could set the validation rule as well as send a message to the user if the rule isn't met. The problem I am having is the form is initialized before the form data is set.
So my question is how can I loop through the array and set the validation rules. If someone could let me know if im along the right tracks with using a switch statement but have code in the wrong place or if there is a different and better approach it would be most helpful thank you
export class ReactiveComponent implements OnInit {
public form: FormGroup;
public fieldList: any;
types: Array<any>;
formData: any;
Param: string;
setData: any;
formLabelNames: any;
get contactFormGroup() {
return this.form.get('inputs') as FormArray;
}
constructor(
private route: ActivatedRoute,
private fb: FormBuilder,
private api: FormService,
private notifiy: NotificationService,
private auth: AuthService,
private router: Router) { }
ngOnInit() {
this.form = this.fb.group({
name: ['', Validators.compose([Validators.required])],
organization: ['', Validators.compose([Validators.required])],
inputs: this.fb.array([this.createForm()])
});
this.route.paramMap.subscribe(params => {
this.Param = params.get('id');
this.getForm(this.Param);
});
// set fieldslist to this field
this.fieldList = this.form.get('inputs') as FormArray;
}
// formgroup
createForm(): FormGroup {
return this.fb.group({
type: ['', Validators.compose([Validators.required])],
name: ['', Validators.compose([Validators.required])],
value: ['', this.validators()]
});
}
getForm(id) {
this.api.getForm(id).subscribe(
(data: any) => this.setForm(data)
);
}
getFieldsFormGroup(index): FormGroup {
const formGroup = this.fieldList.controls[index] as FormGroup;
return formGroup;
}
getContactsFormGroup(index): FormGroup {
const formGroup = this.fieldList.controls[index] as FormGroup;
return formGroup;
}
setForm(data) {
const d = data.results;
this.setData = d;
this.formLabelNames = d[0].fields;
this.form.patchValue({
name: [d[0].form_name],
organization: [d[0].org],
});
this.form.setControl('inputs', this.setExistingFields(d[0].fields));
}
setExistingFields(fields: any): FormArray {
const formArray = new FormArray([]);
this.fieldList = formArray;
fields.forEach(f => {
formArray.push(this.fb.group({
name: f.name,
type: f.type,
value: f.value
}));
});
return formArray;
}
/* This is where I have tried to create a switch statement but I get a undefined error because the setform function is being called after this one */
validators() {
this.formLabelNames.type.forEach((field: any) => {
switch (field.type) {
case 'email':
}
});
}
submit() {
if (this.form.valid) {
const formId = this.Param;
const local = this.auth.decodePayload();
const userId = local.sub;
this.router.navigateByUrl('/dashboard');
this.api.sendForm(this.form.value, formId, userId).subscribe();
this.form.reset();
} else {
this.notifiy.showFailure('Form is not valid', 'Error');
}
}
}
You have several problems, as far as I can judge:
You don't initialize neither the formControls nor the formArray correctly inside of you formGroup. It should rather look like this:
this.form = this.fb.group({
name: new FormControl( "", {validators: [Validators.required]}),
organization: new FormControl( "", {validators: [Validators.required]}),
inputs: this.fb.array([new FormControl(), new FormControl()]),
});
Besides: What is the point of using a formArray when it consists of only one formGroup?
Off course you can set the validators for any abstractControl be it a formControl or a formGroup. For the array it would look like something like this:
this.formArray.controls.map((ctrl) => {
ctrl.setValidators([Validators.required, Validators.email]);
});
It doesn't matter where in the component class you put your method. Though it surely matters when that method is invoked!
My reactive form is almost done. At least it's basic logic. I have only one problem. According to the task I need all inputs to be set using previous data that user entered, if that data exists of course. The data should be kept and stored and rendered if components are switched. I mannaged to save all data into an object in a component, but after page refresh everything is gone.
How can I tackle with this problem?
My code is as follows:
import { Component, OnInit, ViewChild, ElementRef } from '#angular/core';
import { FormGroup, FormControl, FormArray, Validators } from '#angular/forms';
#Component({
selector: 'app-form',
templateUrl: './form.component.html',
styleUrls: ['./form.component.scss']
})
export class FormComponent implements OnInit {
signUpForm: FormGroup;
countries = [
new FormControl('USA'),
new FormControl('India')
];
savedForm: {
city: string,
code: number,
country: string,
email: string,
firstName: string,
id: string,
lastName: string,
phone: number,
state: string;
}
statesUSA = [new FormControl('New York'), new FormControl('California')];
statesIndia = [new FormControl('Andhra Pradesh'), new FormControl('Goa')]
citiesNY = [new FormControl('Albany'), new FormControl('New York City')];
citiesCali = [new FormControl('Sacramento'), new FormControl('Los Angeles'), new FormControl('San Francisco')];
citiesAndhra = [new FormControl('Visakhapatnam'), new FormControl('Amaravati')];
citiesGoa = [new FormControl('Panaji'), new FormControl('Vasco da Gama')];
#ViewChild('phoneInput', {static: false}) phoneInput: ElementRef;
public mask:any = {
mask: '+{38}(0__)000-00-00',
lazy: false
}
constructor() { }
ngOnInit() {
this.signUpForm = new FormGroup({
'firstName': new FormControl(null, [Validators.required, Validators.pattern(/^[а-яА-ЯёЁіІїЇ]{2,32}$/iu)]),
'email': new FormControl(null, [Validators.required, Validators.email, Validators.pattern(/^\S{2,255}#\S+\.\S+$/iu)]),
'country': new FormControl(null, Validators.required),
'phone': new FormControl(null),
'lastName': new FormControl(null, [Validators.required, Validators.pattern(/^[а-яА-ЯёЁіІїЇ]{2,32}$/iu)]),
'id': new FormControl(null, [Validators.required, Validators.pattern(/\b[A-Za-z_]{5,30}\b/)]),
'state': new FormControl(null, Validators.required),
'city': new FormControl(null, Validators.required),
'code': new FormControl(null, [Validators.pattern(/\b[A-Za-z_0-9]{1,10}\b/)])
});
this.signUpForm.setValue(this.savedForm);
}
onBlur(blur: boolean) {
}
onSubmit() {
if(this.signUpForm.status === 'VALID') {
this.
savedForm = this.signUpForm.value;
console.log(this.savedForm);
}
}
onReset() {
}
onChange() {
(<FormGroup>this.signUpForm.get('state').value) = null;
(<FormGroup>this.signUpForm.get('city').value) = null;
}
onOpen(controlName: string) {
}
}
You should use Service, components shouldn't fetch or save data directly they should focus on presenting data and delegate data access to a service.
Step 1
Create a service :
src/services/storeService.ts
import { Injectable } from '#angular/core';
#Injectable({
providedIn: 'root',
})
export class StoreService {
public savedForm
}
When you provide the service at the root level, Angular creates a single, shared instance of StoreService and injects into any class that asks for it. Registering the provider in the #Injectable metadata also allows Angular to optimize an app by removing the service if it turns out not to be used after all.
Step 2
In your component inject the newly created service in the constructor:
#Component({
selector: 'app-form',
templateUrl: './form.component.html',
styleUrls: ['./form.component.scss']
})
export class FormComponent {
signUpForm: FormGroup;
constructor(private storeService: StoreService) { }
ngOnInit() {
this.signUpForm = new FormGroup({
'firstName': new FormControl(null, [Validators.required, Validators.pattern(/^[а-яА-ЯёЁіІїЇ]{2,32}$/iu)]),
'email': new FormControl(null, [Validators.required, Validators.email, Validators.pattern(/^\S{2,255}#\S+\.\S+$/iu)]),
'country': new FormControl(null, Validators.required),
'phone': new FormControl(null),
'lastName': new FormControl(null, [Validators.required, Validators.pattern(/^[а-яА-ЯёЁіІїЇ]{2,32}$/iu)]),
'id': new FormControl(null, [Validators.required, Validators.pattern(/\b[A-Za-z_]{5,30}\b/)]),
'state': new FormControl(null, Validators.required),
'city': new FormControl(null, Validators.required),
'code': new FormControl(null, [Validators.pattern(/\b[A-Za-z_0-9]{1,10}\b/)])
});
if (this.storeService.savedForm) this.signUpForm.setValue(this.storeService.savedForm)
}
onSubmit() {
if (this.signUpForm.status === 'VALID') {
this.storeService.savedForm = this.signUpForm
}
}
}
Is there a way to create a reactive form basing on an existing data model with all the validation magic. In the example below author passes whole new object into a formbuilder but what I want to achieve is elegant way to tell formbuilder what field is required or needs some other validation.
https://malcoded.com/posts/angular-fundamentals-reactive-forms
export class PersonalData {
email: string = '';
mobile: string = '';
country: string = '';
}
...
createFormGroupWithBuilderAndModel(formBuilder: FormBuilder) {
return formBuilder.group({
personalData: formBuilder.group(new PersonalData()),
requestType: '',
text: ''
});
}
I just want to skip the process of assigning a FormControl for each field in the model.
#EDIT
After some research and little hint from #xrobert35 I wanted to try and used https://www.npmjs.com/package/#rxweb/reactive-form-validators
They could be "many" way to do what you want to do, but by just extending your actual solution : Your personnal data could look like :
export class PersonalData {
email: string = ['', Validators.required];
mobile: string = ['', Validators.required, Validators.maxLength(10)];
country: string = '';
}
If you need domain base validation (for reusable purpose) you can use rxweb validations.
export class PersonalData {
#required()
email: string;
#required()
mobile: string;
#greaterThan({ fieldName: 'number2' })
number1: number;
#prop()
number2: number;
}
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
formGroup: FormGroup;
constructor( private validation: RxFormBuilder) {
}
ngOnInit() {
let person: PersonalData = new PersonalData();
this.formGroup = this.validation.formGroup(person);
}
}
If I understand you just want to add validators to your field.
https://angular.io/guide/form-validation
I can't be more precise to giving you the official documentation.
ngOnInit(): void {
this.heroForm = new FormGroup({
'name': new FormControl(this.hero.name, [
Validators.required,
Validators.minLength(4),
forbiddenNameValidator(/bob/i) // <-- Here's how you pass in the custom validator.
]),
'alterEgo': new FormControl(this.hero.alterEgo),
'power': new FormControl(this.hero.power, Validators.required)
});
}
In this exemple the fields name and power are required and of course the syntaxe could differ but the flow is the same.
Does it helps you ?
#EDIT
There is the same post for your use case:
https://stackoverflow.com/a/47576916/7133262
I'm learning Angular testing and so far it has been pretty frustrating. I wanted to test my signup component, but it gave me the following error.
Expected spy AuthenticationService.checkUsername to have been
called.
This is the component I am trying to test. I am currently testing whether the method checkUniqueUsername() works as planned. When I call the message I expect the property uniqueUsernameMessage to contain the string 'already exists' when the authService.checkUsername() method returns {obj: true}
Unfortunately it returns the above error and for the value of the property uniqueUsernameMessage it returns undefined instead of the expected .contains(already exists).
Can someone help me figure out where I went wrong?
Signup.component.ts
#Component({
selector: 'app-sign-up',
templateUrl: 'sign-up.component.html',
styleUrls: ['sign-up.component.css']
})
export class SignUpComponent implements OnInit {
mySignupForm: FormGroup;
countries = countries;
uniqueUsernameMessage;
uniqueEmailMessage;
formSubmitted = false;
#Output() closeSubmenu = new EventEmitter();
constructor(
private authService: AuthenticationService,
private route: Router,
private navigationService: NavigationService){}
ngOnInit() {
this.mySignupForm = new FormGroup({
firstName: new FormControl(null, Validators.required),
lastName: new FormControl(null, Validators.required),
username: new FormControl(null, [Validators.required, Validators.minLength(5), Validators.maxLength(15)]),
birthDate: new FormControl(null, Validators.required),
password1: new FormControl(null, [Validators.required, Validators.minLength(6), Validators.maxLength(15)]),
password2: new FormControl(null, Validators.required),
email: new FormControl(null, [Validators.required, Validators.email]),
country: new FormControl(null, Validators.required),
house: new FormControl(null, Validators.required)
})
}
checkUniqueUsername() {
if ((this.mySignupForm.value.username >= 5 && this.mySignupForm.value.username <= 15) || null ){
this.authService.checkUsername(this.username.value)
.pipe(debounceTime(500))
.subscribe((result: any) => {
if (result.obj) {
console.log('result', result);
this.uniqueUsernameMessage = "This username already exists. Please pick another one."
} else {
this.uniqueUsernameMessage = null;
}
})
}
}
}
signup.component.spec.ts
describe('signup', () => {
let component: SignUpComponent;
let fixture: ComponentFixture<SignUpComponent>;
let authService;
beforeEach(async(() => {
const authServiceSpy = jasmine.createSpyObj('AuthenticationService', ['checkUsername', 'checkEmailUniqueness', 'signup']);
TestBed.configureTestingModule({
declarations: [SignUpComponent],
providers: [
{
provide: AuthenticationService,
useValue: authServiceSpy
},
NavigationService
],
imports: [
RouterTestingModule],
schemas: [NO_ERRORS_SCHEMA]
})
.compileComponents();
authService = TestBed.get(AuthenticationService);
}));
beforeEach(() => {
fixture = TestBed.createComponent(SignUpComponent);
component = fixture.componentInstance;
// fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should warn user if username already exists', fakeAsync(() => {
const fakeUsername = "testUsername"
authService.checkUsername.and.returnValue(of({obj: true}));
fixture.detectChanges();
component.mySignupForm.value.username = fakeUsername;
fixture.detectChanges();
component.checkUniqueUsername();
tick(500);
expect(authService.checkUsername).toHaveBeenCalled();
expect(component.uniqueUsernameMessage).toContain('already exists');
})
)
});
This line:
component.mySignupForm.value.username = fakeUsername;
does not change the value of the FormControl. The username value is still null. Thus, when you check its validity, the condition inside if is false.
Reactive form is immutable. If you want to set new value for any FormControl, it should be done using .setValue().
Like this:
component.mySignupForm.get('username').setValue(fakeUsername);
Hope this helps :)
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 component 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?
You 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 recommande that you FormGroup variable to be a Class attribute and not a OnInit() one.
I also recommande using ReactiveForms with it's API FormControls.
Let me know what happened