Storing dynamic form field as an array in angular - arrays

question.component.ts
ngOnInit() {
this.challengeId = this.route.snapshot.paramMap.get('challengeId');
// this.selectedNoOfAttempt = this.noofattempts[1];
this.myForm = this._fb.group({
question: ['', [Validators.required]],
correctAnswers: this._fb.array([
this.initCorrectAnswer(),
]),
selectedNoOfAttempt: ['', [Validators.required]]
});
}
initCorrectAnswer() {
return this._fb.group({
correctAnswers: ['', Validators.required],
});
}
addAnswer() {
const control = <FormArray>this.myForm.controls['correctAnswers'];
control.push(this.initCorrectAnswer());
}
removeAnswer(i: number) {
const control = <FormArray>this.myForm.controls['correctAnswers'];
control.removeAt(i);
}
question.component.html
<div formArrayName="correctAnswers">
<div *ngFor="let correctAnswers of myForm.controls.correctAnswers.controls; let i=index" class="panel panel-default">
<div class="panel-heading">
<span>Answer {{i + 1}}</span>
<span class="glyphicon glyphicon-remove pull-right" *ngIf="myForm.controls.correctAnswers.controls.length > 1" (click)="removeAnswer(i)">Remove</span>
</div>
<div class="panel-body" [formGroupName]="i">
<div class="form-group col-xs-3">
<input type="text" class="form-control" formControlName="correctAnswers">
<small [hidden]="myForm.controls.correctAnswers.controls[i].controls.correctAnswers.valid" class="text-danger">
Answer is required
</small>
</div>
</div>
</div>
</div>
<div class="margin-20">
<a (click)="addAnswer()" style="cursor: default">
Add another answer +
</a>
</div>
The code above works as it is intended, dynamically insert more fields if needed, and also remove. It also able to return all the dynamic fields, however it is storing the data as
correctAnswers (array)
[0] (object)
correctAnswers: "test" (string)
[1] (object)
correctAnswers: "abc" (string)
I would like to store the data as follow
correctAnswers (array)
[0] "test" (string)
[1] "abc" (string)

In your initCorrectAnswer() function you are adding form group with child form control to your form array, that is why you see it as array of objects. Instead of this, you should add form control:
initCorrectAnswer() {
return this._fb.control('', Validators.required);
}
I also added correctAnswers getter for convenient use in HTML:
get correctAnswers(): FormArray {
return this.myForm.get('correctAnswers') as FormArray;
}
updated add and remove functions:
addAnswer() {
this.correctAnswers.push(this.initCorrectAnswer());
}
removeAnswer(i: number) {
this.correctAnswers.removeAt(i);
}
here is HTML:
<form [formGroup]="myForm">
<div formArrayName="correctAnswers">
<div class="panel panel-default"
*ngFor="let correctAnswer of correctAnswers.controls; index as i">
<div class="panel-heading">
<span>Answer {{i + 1}}</span>
<span class="glyphicon glyphicon-remove pull-right"
*ngIf="correctAnswers.controls.length > 1"
(click)="removeAnswer(i)">Remove</span>
</div>
<div class="panel-body">
<div class="form-group col-xs-3">
<input type="text" class="form-control" [formControlName]="i">
<small [hidden]="correctAnswer.valid" class="text-danger">
Answer is required
</small>
</div>
</div>
</div>
</div>
</form>
StackBlitz: https://stackblitz.com/edit/angular-643srq?file=app%2Fapp.component.html
Official reference

There is no way for you to achieve it through Angular's reactive form, because every form group that is repeated (via your *ngFor) is an object. Note that your form group name is actually your *ngFor index: formGroupName = i; and that your form control is correctAnswers - that corresponds to your object property with name correctAnswers as you have expected it.
You can however, do some post processing though. Simply use an array map() to flatten your object into arrays:
var flattenedArray = this.myForm.correctAnswers.map(x=>x.correctAnswers);

Related

patchValue returning only values of the last index

I have an issue while looping through and array, I'm getting all the index from this array correctly but when I use angular patchValue it updates all the inputs with the last index values and not with their respective values as shown :
I want every input to have theirs, for example, first input values should be "1" (left input => longueur) and "1" (right input => quantity)
I tried with .forEach but no success
CODE SAMPLE
component.ts .forEach
ngOnInit() {
this.requiredFields();
this.service.checkExistQuot().subscribe(res => {
this.quotDetails.forEach( (myArray, index) => {
this.dropForm.patchValue({
longueur: this.quotDetails[index].longueur,
quantity: this.quotDetails[index].quantity
})
console.log(index);
});
});
}
HTML, input example
<div class="products">
<div class="d-flex flex-row" *ngFor="let products of selectedDiam;let i = index">
<input class="number" formControlName="longueur" value="" (change)="postQuotationDatas(products.part_id)" type="number">
</a>
<input class="mb-1 flex1 checkbox" type="checkbox">
<a class="tac flex1"></a>
<a class="flex1 mb-1">
<input class="number" value="" formControlName="quantity" (change)="postQuotationDatas(products.part_id)" type="number">
</a>
<a class="flex1"></a>
</div>
</div>
Your problem is that you only have one form group,dropForm, with 2 controls: quantity and longueur. Even though you have multiple html inputs for longueur and quantity, they are share the same reference in the component
So, with your forEach loop, you are actually patching all your controls for each iteration. That's why you have the same value for all your controls, which is the value for the lasst object the loop went over.
Option #1
A possible solution is to have multiple form groups, like in this stackblitz example
component.ts
//dropForms initialisation
this.quotationDetails.map(() =>
{
let group = this.formBuilder.group(
{
longueur: [''],
quantity: [''],
});
this.dropForms.push(group)
}
this.quotationDetails.forEach( (myArray, index) => {
this.dropForms[index].patchValue({
longueur: this.quotationDetails[index].longueur,
component.html
<div class="d-flex flex-row" *ngFor="let products of quotationDetails; let index=index">
<form [formGroup]="dropForms[index]"> <!-- 1 form group per quotation -->
Option #2
The other solution, to have only 1 formGroup, is to give dynamic control names
component.ts
//Initialisation of dropForm
this.quotationDetails.forEach((val, index)=>
{
group[`longueur_${index}`] = '';
group[`quantity_${index}`] = ''
});
this.dropForm = this.formBuilder.group(
group
)
//Patch
this.quotationDetails.forEach( (myArray, index) => {
let patchValue = {};
patchValue[`longueur_${index}`] = this.quotationDetails[index].longueur;
patchValue[`quantity_${index}`] = this.quotationDetails[index].quantity;
this.dropForm.patchValue(patchValue);
component.html
<form [formGroup]="dropForm">
<div class="products">
<div class="d-flex flex-row" *ngFor="let products of quotationDetails; let index = index">
<a>
<input class="number" formControlName="longueur_{{index}}" value="" type="number">
Stackblitz example
Use FormArray and wrap inputs in arrays.
Example add FormControl to FormArray with FormBuilder:
FormArray - A FormArray aggregates the values of each child FormControl into an array.
in componenent ts:
const EMPLOYEE_FORM = {
firstName: ['', Validators.required],
lastName: ['', Validators.required],
isActive : [false, Validators.required]
}
const COMPANY_FORM = {
employees: new FormArray([], [Validators.required])
}
export class AppComponent {
form: FormGroup;
constructor(private fb: FormBuilder) {
this.form = this.fb.group(COMPANY_FORM);
}
get employees(): FormArray {
return this.form.get('employees') as FormArray;
}
addEmployee() {
const employee = this.fb.group(EMPLOYEE_FORM);
this.employees.push(employee);
}
}
in html
<div *ngFor="let item of employees.controls; let i = index;">
<form [formGroup]="item">
<input type="text" formControlName="firstName" class="form-control" placeholder="FirstName">
<input type="text" formControlName="lastName" class="form-control" placeholder="LastName">
<input class="checkbox" type="checkbox" formControlName="isActive">
</form>
</div>
<button (click)="addEmployee()">add new employee</button>
<div *ngIf="employees.length > 0">
{{employees.value | json}}
</div>
See this link: https://stackblitz.com/edit/add-input-to-formarray-with-frombuilder

create objects on form submit in typescript angular 7

I am trying to create 2 separate objects on form submission. Below is my code:
test.html
<form [formGroup]="gamificationForm" (ngSubmit)="onSubmit()">
<div *ngFor="let medal of medalResponse; let iMedal=index" class="row col-lg-12 mtop-custom-10 medal-level-point-config">
<input type="hidden" class="point-text" [(ngModel)]="medal.medalId" formControlName="medalId" />
<div class="col-lg-4 col-6">
{{medal.medalName}}
<input type="hidden" class="point-text" [(ngModel)]="medal.medalName" formControlName="medalName" />
</div>
<div class="col-lg-4 col-6">
<input type="number" class="point-text" [(ngModel)]="medal.points" formControlName="points" />
</div>
</div>
<button class="custom-btn-class" name="submit" type="submit">Save</button>
</form>
Typescript:
gamificationForm = this.fb.group({
medals: this.fb.group({
medalId: [''],
medalName: [''],
points: ['']
})
});
onSubmit() {
console.warn(this.gamificationForm.value);
}
JSON:
It is printing last values from the loop on html page and in json too.
You can convert this JSON value to your required format,
format(value) {
return [
{
Level: [
{
"levelId-0":value.levelId-0,
"level-0":value.level-0,
"points-0":value.points-0
},
....
],
Medal: [
.....
]
}
]
}
Unfortunately I haven't done this via Template-driven Forms, but here my ideas:
You will need to group your fields into the Level and Medal arrays by wrapping your *ngFor into a <ng-container formArrayName="Level"> container and try it.
Each *ngFor should set the position FormArray, but you will need to experiment and see if the formArrayName directive does it for you.
Happy ng-coding!

Binding values from ng-repeat in Angularjs

I'm loading data from a database and save it to an array as follows:
$scope.allGroups = [{id: 1, groupName: group1}, {id: 2, groupName: group2}, ..]
In my view, I'm trying to select multiple group names as follows:
<div
class="drag-container avaliable-groups-connect groups-container schedule-container"
>
<div class="group" ng-repeat="m in allGroups">
<input type="checkbox" ng-model="m.selected" />
<span>{{ m.groupName }}</span>
</div>
</div>
I want to save selected items (m.selected) to an array one-by-one and bind that array to ng-model="schedule.selectedGroups"
How can I perform that? Thank you..
<div class="group" ng-repeat="m in allGroups" ng-init="m.selected = false">
<input type="checkbox" ng-model="m.selected" />
<span>{{ m.groupName }}</span>
</div>
Now your checkbox ng-model with modify the variable to true or false and in your js code
you can do like below.
$scope.schedule.selectedGroups = $scope.allGroups.filter(function (data) {
return data.selected === true;
});

How to get dynamic ng-model from ng-repeat in javascript?

I'm developoing a web app and stuck here:
Part of the HTML:
<div class="input-group">
<select name="select" class="form-control input-group-select" ng-options="key as key for (key , value) in pos" ng-model="word.pos" ng-change="addPos()">
<option value="">Choose a POS</option>
</select>
<span class="sort"><i class="fa fa-sort"></i></span>
</div>
<ul class="listGroup" ng-show="_pos.length > 0">
<li class="list" ng-repeat="item in _pos track by $index">
<span>
{{item.pos}}
<span class="btn btn-danger btn-xs" ng-click="delPos($index)">
<span class="fa fa-close"></span>
</span>
</span>
<!-- I wanna add the input which can add more list item to the item.pos-->
<div class="input-group">
<a class="input-group-addon add" ng-class=" word.newWordExp ? 'active' : ''" ng-click="addItemOne()"><span class="fa fa-plus"></span></a>
<input type="text" class="form-control exp" autocomplete="off" placeholder="Add explanation" ng-model="word.newWordExp" ng-enter="addExpToPos()">
{{word.newWordExp}}
</div>
</li>
</ul>
Part of the js:
$scope._pos = [];
$scope.addPos = function () {
console.log("You selected something!");
if ($scope.word.pos) {
$scope._pos.push({
pos : $scope.word.pos
});
}
console.dir($scope._pos);
//console.dir($scope.word.newWordExp[posItem]);
};
$scope.delPos = function ($index) {
console.log("You deleted a POS");
$scope._pos.splice($index, 1);
console.dir($scope._pos);
};
$scope.addItemOne = function (index) {
//$scope.itemOne = $scope.newWordExp;
if ($scope.word.newWordExp) {
console.log("TRUE");
$scope._newWordExp.push({
content: $scope.word.newWordExp
});
console.dir($scope._newWordExp);
$scope.word.newWordExp = '';
} else {
console.log("FALSE");
}
};
$scope.deleteItemOne = function ($index) {
$scope._newWordExp.splice($index, 1);
};
So, what am I wannt to do is select one option and append the value to $scope._pos, then display as a list with all of my selection.
And in every list item, add an input filed and add sub list to the $scope._pos value.
n.
explanation 1
explanation 2
explanation 3
adv.
explanation 1
explanation 2
So I don't know how to generate dynamic ng-model and use the value in javascript.
Normaly should like ng-model="word.newExplanation[item]" in HTML, but in javascript, $scope.word.newExplanation[item] said "item is not defined".
can any one help?
If I've understood it correclty you could do it like this:
Store your lists in an array of object this.lists.
The first object in the explanation array is initialized with empty strings so ng-repeat will render the first explanation form.
Then loop over it with ng-repeat. There you can also add dynamically the adding form for your explanation items.
You can also create append/delete/edit buttons inside the nested ng-repeat of your explanation array. Append & delete is already added in the demo.
Please find the demo below or in this jsfiddle.
angular.module('demoApp', [])
.controller('appController', AppController);
function AppController($filter) {
var vm = this,
explainTmpl = {
name: '',
text: ''
},
findInList = function (explain) {
return $filter('filter')(vm.lists, {
explanations: explain
})[0];
};
this.options = [{
name: 'option1',
value: 0
}, {
name: 'option2',
value: 1
}, {
name: 'option3',
value: 2
}];
this.lists = [];
this.selectedOption = this.options[0];
this.addList = function (name, option) {
var list = $filter('filter')(vm.lists, {
name: name
}); // is it in list?
console.log(name, option, list, list.length == 0);
//vm.lists
if (!list.length) {
vm.lists.push({
name: name,
option: option,
explanations: [angular.copy(explainTmpl)]
});
}
};
this.append = function (explain) {
console.log(explain, $filter('filter')(vm.lists, {
explanations: explain
}));
var currentList = findInList(explain);
currentList.explanations.push(angular.copy(explainTmpl));
}
this.delete = function (explain) {
console.log(explain);
var currentList = findInList(explain),
index = currentList.explanations.indexOf(explain);
if (index == 0 && currentList.explanations.length == 1) {
// show alert later, can't delete first element if size == 1
return;
}
currentList.explanations.splice(index, 1);
};
}
AppController.$inject = ['$filter'];
<link href="http://maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css" rel="stylesheet"/>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="demoApp" ng-controller="appController as ctrl">
<select ng-model="ctrl.selectedOption" ng-options="option.name for option in ctrl.options"></select>
<input ng-model="ctrl.listName" placeholder="add list name" />
<button class="btn btn-default" title="add list" ng-click="ctrl.addList(ctrl.listName, ctrl.selectedOption)"><i class="fa fa-plus"></i>
</button>
<div class="list-group">Debug output - current lists:<pre>{{ctrl.lists|json:2}}</pre>
<div class="list-group-item" ng-repeat="list in ctrl.lists">
<h2>Explanations of list - {{list.name}}</h2>
<h3>Selected option is: {{ctrl.selectedOption}}</h3>
<div class="list-group" ng-repeat="explain in list.explanations">
<div class="list-group-item">
<p class="alert" ng-if="!explain.title">No explanation here yet.</p>
<div class="well" ng-if="explain.title || explain.text">
<h4>
{{explain.title}}
</h4>
<p>{{explain.text}}</p>
</div>Title:
<input ng-model="explain.title" placeholder="add title" />Text:
<input ng-model="explain.text" placeholder="enter text" />
<button class="btn btn-primary" ng-click="ctrl.append(explain)">Append</button>
<button class="btn btn-primary" ng-click="ctrl.delete(explain)">Delete</button>
</div>
</div>
</div>
</div>
</div>

Get data, change one single element and return to api

I get Data from an api and easily list them, now i want to change one element of those, to "default". I dont know how to control that,
Here is the js.
$scope.updateDefault = function() {
if (AuthService.isAuth()) {
Account.one().get().then(
function(account) {
**account.data.defaultMsisdn= $scope.element.isSelected;
$log.d("account: ", account.data);
account.data.put();**
}
);
}
};
here is the HTML:
<div id="container" style="width:650px">
<ol ng-repeat="element in accountmsisdn">
**<li style=" text-align:left; float:left">
<input type="radio" ng-model="element.isSelected" ng-click="updateDefault()">
<span>{{element.msisdn}} </span>
</li>**
<li style="text-align:center;float: inside" ng-switch="element.active">
<span ng-switch-when=true>
{{'view.settings.sender.active'| translate}}</span>
<span ng-switch-when=false>
{{'view.settings.sender.notactive'| translate}}</span>
<span ng-switch-default>
<strong> - </strong> </span>
</li>
You need to pass the current element as an argument to updateDefault(), so it knows what element to update:
<input type="radio" ng-model="element.isSelected" ng-click="updateDefault(element)" />
$scope.updateDefault = function (element) {
...
account.data.defaultMsisdn = element.isSelected;
..
};

Resources