Fetch input values created by ngFor on button click Angular - javascript

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.

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"
>

Adding formcontrol,formgrops and formarray to duplicated html angular

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

Angular-FormArray: Creating a table form from 2-dimensional Javascript array

I need to create a table form with Angular which is based on a simple 2-dimensional array of arrays.
The data structure is simple but the data in the cells is interdependent. The single cells are accessed by indices alone e.g., row:1, col:0.
Custom validators need to be defined on on cell-level. In addition, there may be validators for rows and columns.
I tried various ways to define FormArray holding an array of FormControls. But I am unsure how to access the respective FormControl by row and column indices alone in the Angular template.
Model
[
['a', 'b'],
['c', 'd']
]
FormGroup
form = new FormGroup({
rows: new FormArray([...])
});
Expected result
I am tried various things similar to this:
<form [formGroup]="form"">
<div formArrayName="rows">
<div
*ngFor="let row of rows.controls; let rowIndex = index" [formGroupName]="rowIndex">
<div formArrayName="cols">
<div
*ngFor="let col of form.get('cols').controls; let colIndex = index"
[formGroupName]="colIndex">
<input [formControlName]="colIndex" />
</div>
</div>
</div>
</div>
</form>
Dabbel, if you has an Array of Array, create a FormArrays of FormArrays (sorry for the joke)
Well, imagine you has
data=[
['a', 'b'],
['c', 'd']
]
You can in ngOnInit create the formArray of FormArray like
//At firs a empty FormArray
this.formArray = new FormArray([]);
//with each element of data
this.data.forEach(x => {
//x is e.g.['a','b']
//we create a emptt FormArray
const obj = new FormArray([]);
//add a FormControl
x.forEach(y => {
obj.push(new FormControl(y));
});
//and push in the formArray
this.formArray.push(obj);
});
or abreviated using map like
this.formArray=new FormArray(
this.data.map(x=>new FormArray(
x.map(y=>new FormControl(y))))
)
Well, How mannage a FormArray outside a FormGroup? If our FormArray is a FormArray of FormGroup, we make in general
<!--yes we can use [formGroup] with a FormArray-->
<form [formGroup]="formArray">
<!--iterate over the formArray.controls, that is a formGroup-->
<div *ngFor="let group of formArray.controls;let i=index">
<div [formGroup]="group">
<input formControlName="name">
...
</div>
</div>
</form>
Well, our formArray is a FormArray of FormArray, but remember that we using [formGroup] with an array and iterate over formArray.controls.
<form [formGroup]="formArray">
<div *ngFor="let subarray of formArray.controls;let i=index">
<div [formGroup]="subarray">
<ng-container *ngFor="let control of subarray.controls;let j=index">
<input [formControl]="control">{{control.invalid?"*":""}}
</ng-container>
</div>
</div>
</form>
NOTE: I use <ng-container> nor <div> to not create addicionals divs. (We can not put the *ngFor in the own input because, then, we can not have access to control.invalid
Well, as you want create Validators, we are changing a bit when we create the formGroup to include the validators, I put a "fool" example
this.formArray=new FormArray(
this.data.map(x=>new FormArray(
x.map(y=>new FormControl(y,Validators.required)),
this.rowValidator())),this.arrayValidator()
)
And ours validators can be like
rowValidator()
{
return (array:FormArray)=> {
const invalid:boolean=array.value.
filter((x,index)=>array.value.indexOf(x)!=index).length>0
return invalid?{error:'must be different'}:null
}
}
arrayValidator()
{
return (array:FormArray)=> {
let arrayJoin="";
array.value.forEach(x=>arrayJoin+=x.join(''))
return arrayJoin=="abcd"?null:{error:'must be a,b,c,d'}
}
}
You can see in the stackblitz
NOTE: In a real application, we neen't use so many Validators. Take account the cost of this validator on the perfomance of the app
Template forms is going to make your life a lot easier here.
Just bind directly to the array itself.
<form>
<div *ngFor="let row of data;let i=index">
<input [name]="prop1_ + i" [(ngModel)]="row.prop1">
<input [name]="prop2_ + i" [(ngModel)]="row.prop2">
</div>
</form>

Auto select checkboxes from data within Form Array

I have a list of skills which is fetched from the DB like so:
<div class="uk-margin">
<div *ngFor="let data of skills">
<div *ngFor="let skill of data.allSkills">
<input type="checkbox"> {{skill}}<br>
</div>
</div>
</div>
The above outputs:
My form modal looks like this:
this.userForm = this._fb.group({
name: ['', [Validators.required]],
skills: this._fb.array([]),
});
I then fetch the checkbox values for the logged In user that they selected at some other time. As I am using Reactive Forms I patch the returned values from the DB for the user like so:
// Patch Skills
let ctrl = <FormArray>this.userForm.controls.skills;
this.user.skills.forEach(item => {
ctrl.push(new FormControl(item))
});
// Patch Other things
this.userForm.patchValue({
name: this.user.name,
});
If I console.log(ctrl.controls); I get the following:
I have a test
I have a nothing
How can I auto select the checkbox items that have been returned from the DB for that user (so that "I have a nothing" & "I have a test" are auto checked) and also update the formArray incase the user checks or unchecks values before sending it to the DB?
In your HTML, use the checked property if your skill is contained in ctrl.controls
<div class="uk-margin">
<div *ngFor="let data of skills">
<div *ngFor="let skill of data.allSkills">
<input type="checkbox" [checked]="ctrl.controls.includes(skill)"> {{skill}}<br>
</div>
</div>
</div>
I think this would help you!
Solution:
Component Code :
let formArray: Array<any> = [];
for (let skills of data.allSkills) {
formArray.push(new FormControl(skills.value));
}
let formGroup = {};
this.fieldName = 'skillGroup'
formGroup[this.fieldName] = this._formBuilder.array(formArray);
this._fromGroup = this._formBuilder.group(formGroup)
Template :
Edit:
Live working URL: https://plnkr.co/edit/NC34MrQFbk4hNamOzsZF?p=preview

Is there a way to bind input to value and track with different ng-model?

This is my problem:
I'm using ng-repeat to create input fields from an array. [a, b, c,...]
I have a button that lets you add more blank input fields.
After clicking the save button I want to be able to create a new array from the input fields.
So I'm thinking of having an iterated ng-model (item-1, item-2, item-3,...) tracking each of these inputs, then iterate through them when I hit save. But that requires me to have each input binded with a new variable that's not from [a, b, c,....].
So what I'm asking is how I can do this pseudo code with AngularJS.
<input ng-repeat="letter in letters"
set-value="letter.value"
bind-to-model="someNewVariable">
See this example:
Html :
<div class="addcontend">
<div ng-repeat="item in inputs track by $index">
<input ng-model="inputs[$index]" ng-value="item" />
<button ng-click='getVal(item)'>get input value</button>
</div>
</div>
<button ng-click="addfield()">Add field</button>
<button ng-click="log()">log</button>
Controller :
app.controller('cntr',function($scope){
$scope.inputs = ['a', 'b'];
$scope.addfield = function(){
$scope.inputs.push('')
}
$scope.getVal = function(v){
alert(v);
}
$scope.log = function() {
console.log($scope.inputs)
}
})

Categories