How do i display a FormArray of GroupLists? - javascript

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

Related

Update image in Angular 11

I have a task to develop a system that can update each employee's image in angular
form: FormGroup;
departments: FormGroup;
public fb: FormBuilder as instantiated in the constructor
this.form = this.fb.group({
companyName: [''],
companyAddress: [''],
department: this.fb.array([]),
})
createDepartment() {
this.departments = this.fb.group({
id: [''];
code: [''],
employees: this.fb.array([]),
});
}
get departmentControls() {
return (this.form.get(department) as FormArray).controls
}
export interface Department {
id: number;
code: string;
employees: Employee[];
}
export interface Employee {
id: number;
path: string;
name: string;
}
Here, imagePath is an s3 bucket URL
<div
formArrayName="department"
*ngFor="
let department of departmentControls;
let departmentIndex = index
"
>
<div [formGroupName]="departmentIndex">
// departmentIndex form group
<div
formArrayName="employee"
*ngFor="
let employee of department.get('employees').controls;
let employeeIndex = index
"
>
<div [formGroupName]="employeeIndex">
<input
type="file"
hidden
(change)="onUpdateHandler($event, department.value.id, employee.value.path)"
[id]="'department-image-input-' + employeeIndex"
/>
<img [src]="`${imagePath}${employee.value.path}?tr=w-200,h-200`" class="image-wrapper__img" />
<button
nbButton
status="warning"
(click)="onImageChangeClick(departmentIndex, employeeIndex)"
>
Change
</button>
</div>
</div>
</div>
</div>
I need to open a particular index file to update the image path
below code is tried but excepted image is not changed
onImageChangeClick(departmentIndex: number, employeeIndex: number) {
// below code needs to change
const element = document.getElementById(`department-image-input-${employeeIndex}`) as HTMLInputElement;
element.click();
}
**for example, when 0 departmentIndex's department has 1 employee and 1 departmentIndex's
the department has 1 employee when I click departmentIndex 1 employee's change button to
change image it change 0 departmentIndex's 0 employeeIndex employee image**
if departmentIndex 1 has 3 employees and departmentIndex 2 has 5 employees;
When I click departmentIndex 2 's employeeIndex 2 's image it changes departmentIndex=1 's employeeIndex 2's image
onUpdateHandler(event: Event, departmentId: number, path: string) {
const target = event.target as HTMLInputElement;
const file = target.files[0];
const data = new FormData();
data.append('file', file, file.name);
data.append('path', path);
// code for sent data to backend
}

Problem with storing data in Firebase (_isScalar: false)

I have created a form that stores the course type data in the Firebase. When I try to save a new element it creates this: https://i.stack.imgur.com/jXO1z.jpg.
Here's my code in create-course Component:
form: FormGroup = this.fb.group({
name: [''],
code: [''],
ects: [''],
lekt: ['']
})
constructor(private fb: FormBuilder, private dataStorageService : DataStorageService) { }
ngOnInit(): void {}
onSaveData(){
this.dataStorageService.storeCourses();
}
Here's the code in the service component :
storeCourses(){
const courses = this.courseService.getCourses();
this.http.put(
'https://projekti-ff52b-default-rtdb.firebaseio.com/courses.json',
courses)
.subscribe(response => {
console.log(response);});
}
getCourses() {
return of(this.courses); }
private courses: Course[] = [];
And Course [] is an exported interface from course model.ts

Add/ remove custom validators to form array in angular 8

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!

Angular reactive form based on model with validation

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

Unable to patch data to FormArray

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.

Categories