Adding formcontrol,formgrops and formarray to duplicated html angular - javascript

I am doing an add form for that I need the formcontrol,formgroup setup for all these values entered in the ts. Its normal stuff maybe. But here I have it very different. I have a add button which duplicates the set of forms in the html. I am finding it difficult to write formgroup logic and validators in the ts for it.
Kindly help if u know(You can atleast tell me how to set up the formcontrol names ang bring it on to ts)
Note: Pls comment below if my question was unclear.
My stackblitz link : https://stackblitz.com/edit/angular-ivy-g8cty1?file=src%2Fapp%2Fapp.component.html

You can declare your FormGroup like below.
myForm = new FormArray([]);
addRow() {
const group = new FormGroup({
firstName: new FormControl("", [Validators.required]),
value: new FormControl("", [Validators.required])
});
this.myForm.push(group);
}
and invoke this function whenever user click on Add button.
In Template to show the form. Loop through FormControl and bind it like below.
<div *ngFor="let form of myForm.controls;">
<ng-container [formGroup]="form">
<div>
<label>Name</label
><input type="text" required formControlName="firstName" />
</div>
<span class="text-danger" *ngIf="isInValidFormControl(form,'firstName')">
Name is required
</span>
<div>
<label>Value</label><input type="text" formControlName="value" />
</div>
</ng-container>
</div>
Your validation with respect to total will be like below.
isValidTotal() {
var values = this.myForm.controls.map((x) =>
x ? Number(x.value.value) : 0
);
let sumTotal = values.reduce((a, b) => a + b);
return sumTotal <= this.total;
}
And you can call this from template like this.
<button (click)="addRow()" [disabled]="!isValidTotal()">Add</button>
<button (click)="submit()" [disabled]="!isValidTotal()">Submit</button>
<div *ngIf="!isValidTotal()">
<p>{{errorMessage}}</p>
</div>
Working sandbox
https://codesandbox.io/s/formarraydynamic-rqdhc?file=/src/app/app.component.html

Related

Reactive form validation for dynamic and hidden fields

First off, I have an Angular reactive form that has a button that can add another FormArray to the form. All validation good and working as expected. Things have gotten a little tricky when introducing another dynamic control to the already dynamic form group. This control is shown/hidden based on a selection made in another form control.
When the control is shown I introduce validation, when the control is hidden the validation is cleared. This ensures that my form remains valid/invalid correctly.
Its acting a little buggy e.g. when I complete a group of inputs and add another dynamic group of inputs, both triggering the hidden control... then to amend the previous hidden input - the form remains true. e.g.
Selecting 123 triggers the "Other Code" control, removing the value should make the form invalid, but it stays true at this point.
I have a change function assigned to the select to determine whats been selected.
selectClick(x) {
const f = this.form;
let items = this.form.get('justificationItems') as FormArray;
if (x === '123') {
items.controls.forEach(i => {
console.log(i)
i['controls'].other.setValidators([Validators.required]);
// i['controls'].other.updateValueAndValidity();
});
} else {
items.controls.forEach(i => {
i['controls'].other.clearValidators();
// i['controls'].other.updateValueAndValidity();
});
}
f.updateValueAndValidity();
}
I suspect when changing the select property to trigger the above it does not do it to the correct index item, and it does it for all?
StackBlitz - https://stackblitz.com/edit/angular-prepopulate-dynamic-reactive-form-array-ufxsf9?file=src/app/app.component.ts
the best way to "clear/add Validators" really is enabled or disabled the formControls. Remember a formControl has as status one of this:
type FormControlStatus = 'VALID' | 'INVALID' | 'PENDING' | 'DISABLED';
So we can simple enabled/disabled the FormControl. Futhermore, when we create the formGroup we can created disabled, so at first will be not INVALID
Well, the code is a bit confussed. first the use of i['controls'].other (really you can use i.controls.other an a estrange mix using FormBuilder and new Form.
As always we has a FormArray we create a getter
get justificationItems()
{
return this.form.get('justificationItems') as FormArray;
}
In stead use two differents functions to create the form, we can use and unique
createJustificationField(x: any=null): FormGroup {
x=x || {name:null,description:null,code:null,other:null}
return new FormGroup({
name: new FormControl(x.name, [Validators.required]),
description: new FormControl(x.description, [Validators.required]),
code: new FormControl(x.code, [Validators.required]),
other: new FormControl({value:x.other,
disabled:x.code!=='123'},[Validators.required]),
});
}
See that we can use as
this.createJustificationField(..an object..)
this.createJustificationField()
Our functions: createForm, addItem and selectClick (I like more another name like codeChange but is a minor change) becomes like
createForm() {
this.service.getmodel().subscribe((response:any) => {
this.form = new FormGroup({
justificationItems: new FormArray(
response.justificationItems.map(x=>
this.createJustificationField(x))
),
});
});
}
addItem(): void {
this.justificationItems.push(this.createJustificationField());
this.form.updateValueAndValidity();
}
selectClick(x,i) {
if (x === '123')
this.justificationItems.at(i).get('other').enable()
else
this.justificationItems.at(i).get('other').disable()
this.form.updateValueAndValidity();
}
And the .html becomes more clear in the way
<form *ngIf="form" [formGroup]="form">
<div formArrayName="justificationItems">
<div
*ngFor="
let orgs of justificationItems.controls;
let i = index;
let last = last
"
[formGroupName]="i"
>
<label>Name </label>
<input formControlName="name" placeholder="Item name" /><br />
<label>Description </label>
<input
formControlName="description"
placeholder="Item description"
/><br />
<label>Code </label>
<select
(change)="selectClick($event.target.value, i)"
formControlName="code"
>
<option value="123">123</option>
<option value="456">456</option></select
><br />
<ng-container *ngIf="justificationItems.at(i).value.code === '123'">
<label>Other Code </label>
<input formControlName="other" placeholder="other" /><br /><br />
</ng-container>
<button
*ngIf="last"
[disabled]="justificationItems.at(i).invalid"
type="button"
(click)="addItem()"
>
Add Item
</button>
</div>
</div>
<button [disabled]="!form.valid" type="button">Submit</button>
</form>
<p>Is form valid: {{ form?.valid | json }}</p>
see the stackblitz
Root cause: When selectClick trigger, you clear or set validation for all controls other in array form. You should set only for one form in formArray.
I rewrite your function:
selectClick(x, index) {
const f = this.form;
let items = this.form.get('justificationItems') as FormArray;
if (x === '123') {
items.controls[index]['controls'].other.setValidators([Validators.required]);
} else {
items.controls.forEach(i => {
items.controls[index]['controls'].other.clearValidators();
i['controls'].other.updateValueAndValidity();
});
}
items.controls[index]['controls'].other.updateValueAndValidity();
}
change code in template:
<select
(change)="selectClick($event.target.value, i)"
formControlName="code"
>

Fetch input values created by ngFor on button click Angular

I have an input field which is being generated using *ngFor and I want to capture all those values inside the input field on submit button click. I have tried all possible ways but not able to get the entered data, please help me. Below is my code I have tried.
html Code:
arr= [{name:"test", percentage: "29"},{name:"abc", percentage: "45"}, {name:"def", percentage: "63"}]
<div *ngFor= "let obj of arr">
<input type="text" [value]="obj.percentage">
</div>
<button (click)="submit()"></button>
Your current solution does not have any sort of form manager. There are two solutions:
Reactive Forms
// component.ts
form = new FormGroup( {
test: new FormControl(null)
// Add your other fields here
} );
// Patch your values into the form
arr.forEach(value => {
this.form.get(value.name).patchValue(value.percentage);
})
// html
<div *ngFor= "let obj of arr">
<input type="text" [formControl]="form.get(obj.name)" >
</div>
Your form will store all the values. To get them once, you could use form.value
NgModel
// component.ts
test: boolean;
// Add your other fields here
// Patch your values into the form
arr.forEach(value => {
this.[value.name] = value.percentage;
})
// html
<div *ngFor= "let obj of arr">
<input type="text" [(ngModel)]="obj.name" >
</div>
Both of these examples are cutting corners but they should give you enough of an idea of where to head next. I'd suggest the Reactive Forms path.

How add dynamic fields in angular form

I have to create a dynamic form where label and input fields will both are text-field(user will fill at run time). And user can add as many as fields he requires. I have requirements where I have to generate. property file in backend. Where key-value pair is required and the user decides how much key-value pair he needs.
Create FormGroup as
keyValueFA = new FormArray([this.newKeyValueFG]);
propertyFG = new FormGroup({
keyValue: this.keyValueFA,
});
getter to create new FormGroup
get newKeyValueFG(): FormGroup {
return new FormGroup({
key: new FormControl(null),
value: new FormControl(null),
});
}
Other methods to set, remove, get FormGroup
get keyValueArrayFGControls(): FormGroup[] {
return this.keyValueFA.controls as FormGroup[];
}
addNewKeyValueFG(): void {
this.keyValueFA.push(this.newKeyValueFG);
}
removeNewKeyValueFG(index: number): void {
this.keyValueFA.removeAt(index);
}
In template iterate keyValueArrayFGControls
<form [formGroup]="propertyFG">
<div
formArrayName="keyValue"
*ngFor="let fg of keyValueArrayFGControls; let i = index"
>
<div [formGroup]="fg">
{{ i + 1 }})
<div><input type="text" formControlName="key" placeholder="Key" /></div>
<div>
<input type="text" formControlName="value" placeholder="Value" />
</div>
<button (click)="removeNewKeyValueFG(i)">Remove</button>
</div>
</div>
<button (click)="addNewKeyValueFG()">Add</button>
</form>
Working demo

How to assign values for multiple tds of same formcontrolname on angular

I have many input types created within table by ngFor.There are other variables created with iteration as well
TS
cars=[{id:1232,name:Toyota,bhp:500},
{id:4321,name:Mclaren,bhp:720},
{id:2321,name:Mercedes,bhp:470},
{id:4321,name:Subaru,bhp:342},
{id:5432,name:Mazda,bhp:4321}]
CarForm= new FormGroup({
carNumber: new FormControl(),
});
HTML
<tr *ngFor = "let car of cars" >
<td>
<input type="text" formControlName="carNumber"">
</td>
</tr>
I know that Im able to pass the name value to input like this :
CarForm.controls.carNumber.setValue(id).
But in this way Im not able to pass specifically row by row.
also changed my HTML like below but didnt work
[value]="cars(index).id"
How can I do that?
A good approach as mentioned by #Argee is to use FormArray. The below approach uses FormBuilder to build the FormGroup
In your TS File
cars=[
{id:1232,name:'Toyota',bhp:500},
{id:4321,name:'Mclaren',bhp:720},
{id:2321,name:'Mercedes',bhp:470},
{id:4321,name:'Subaru',bhp:342},
{id:5432,name:'Mazda',bhp:4321}]
CarForm = this.fb.group({
cars: this.fb.array(
this.cars.map(({id, name, bhp}) =>
this.fb.group({
id: [id],
name: [name],
bhp: [bhp]
})
)
)
})
get carsArray(): FormArray {
return this.CarForm.get('cars') as FormArray;
}
constructor(private fb: FormBuilder) {
}
In your HTML
Form Value: <br>
<pre>{{CarForm.value | json }}</pre>
<form [formGroup]='CarForm'>
<div formArrayName='cars'>
<div *ngFor="let car of carsArray.controls; let i = index" [formGroupName]='i'><br>
<label [attr.for]="'car-name' + i">Car {{ i + 1 }} Name </label>
<input formControlName='name'><br>
<label [attr.for]="'car-bhp' + i">Car {{ i + 1 }} Bhp </label>
<input formControlName='bhp'>
</div>
</div>
</form>
See Sample Code on Stackblitz

How to concatenate values using Reactive Forms?

I want to concatenate 2 values into 1 label using Reactive Forms.
In this case i'm not using ngModel binding.
<label
id="identificationCode"
name="identificationCode"
formControlName="lblIdCode">______</label>
<input type="text"
id="reference"
name="reference"
formControlName="txtReference"
maxlength="250"
(change)="handleIdCode($event)">
<input type="text"
id="publicacion"
name="publicacion"
formControlName="txtPublicacion"
maxlength="250"
(change)="handleIdCode($event)">
I want to concatenate those 2 input text when user is writing and automatically reflect the value into the label. Is there any way like we do it with model binding without change event??
Use label to display the information. The label is not meant to bind with Reactive Form. If you need concatenate values to pass to API or for any use then try on TS. User cannot change the value of Label so there is no point to bind it, but just display the concatenated value.
Remove formControlName="lblIdCode" from your label and add for attribute.
<label>{{form.get('txtReference').value}} - {{form.get('txtPublicacion').value}}</label>
And concatenate on TS:
const lblIdCode = this.form.get('txtReference').value + this.form.get('txtPublicacion').value
The definition of label:
The HTML element represents a caption for an item in a user interface.
https://developer.mozilla.org/en-US/docs/Web/HTML/Element/label
Although given answers work fine. There could be another declarative approach which will take advantage of valueChanges observables of the input text. We can combine the input texts' valuechanges observables and map to the desired output i.e. concatenate the Reference + Publicacion like this:
Component.ts:
export class FormreactiveComponent implements OnInit {
lblIdCode$: Observable<string>;
form = new FormGroup({
txtReference: new FormControl(''),
txtPublicacion: new FormControl('')
});
constructor() { }
ngOnInit() {
const refCtrl = this.form.get('txtReference');
const pubCtrl = this.form.get('txtPublicacion');
this.lblIdCode$ = combineLatest(refCtrl.valueChanges.pipe(startWith(refCtrl.value)),
pubCtrl.valueChanges.pipe(startWith(pubCtrl.value)))
.pipe(map(([firstName, lastName]) => {
return `${firstName} ${lastName}`;
}));
}
}
Template:
<form name="form" [formGroup]="form">
<div class="form-group">
<label for="txtReference">Reference</label>
<input type="text" class="form-control" formControlName="txtReference"/>
</div>
<div class="form-group">
<label for="txtPublicacion">Publicacion</label>
<input type="text" class="form-control" formControlName="txtPublicacion"/>
</div>
<div class="form-group">
<label for="lblIdCode">{{lblIdCode$ | async}}</label>
</div>
</form>
Working example
One approach is that you can use reference variables to refer to controls within the template. Like so
<label
id="identificationCode"
name="identificationCode">{{reference.value + " - " + publicacion.value}}</label>
<input type="text"
id="reference"
name="reference"
formControlName="txtReference"
maxlength="250"
#reference>
<input type="text"
id="publicacion"
name="publicacion"
formControlName="txtPublicacion"
maxlength="250"
#publicacion>
The important parts are the #reference and #publicacion on each of the inputs. This links the controls to the variables.
You can then use these variables within an Angular interpolation block like {{reference.value + " - " + publicacion.value}}. You can combine the values however you want inside this block.
Try using the form values, just like you'd use in the .ts file.
<label
id="identificationCode"
name="identificationCode"
formControlName="lblIdCode">
{{form.value.reference + ' ' + form.value.publicacion}}
</label>
you can create a get property base of these values like in
componenpe ts
get referencePublicacionValues() : string {
return `${this.form.get(txtReference).value} ${this.form.get('txtPublicacion').value}`
}
template
<label
id="identificationCode"
name="identificationCode">
{{referencePublicacionValues}}
</label>
now you have the value in reference Publicacion Values property any change the value will reflect to the ui
you can't use formControlName directive on labels if you want to set
the formcontrol lblIdCode you can use form valueChanges
this.form.valueChanges.subscribe( () => {
this.form.get('lblIdCode').setValue(this.referencePublicacionValues,{emitEvent: false})
})
demo 🔥🔥

Categories