Angular 2 Reactive Form with checkboxes - javascript

Recently I've been trying to figure out how those Reactive Forms works. Basic examples (no nesting etc.) are understandable however if I have a form structure like this with checkboxes:
{
"username": "",
"damnIt": "",
"checkboxes": [
{
"checked": false,
"name": "Checked1",
"value": 10
},
{
"checked": false,
"name": "Checked1",
"value": 11
},
{
"checked": false,
"name": "Checked1",
"value": 12
}
]
}
How should I render it on the template in order to change the "*checked" value (true | false)?

You could do something like this:
<form #theForm="ngForm" (ngSubmit)="submitForm(theForm.value)">
<div *ngFor="let cb of data.checkboxes">
<label>
<input type="checkbox" [name]="cb.name" [(ngModel)]="cb.checked">{{cb.value}}
</label>
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
<pre>{{data.checkboxes|json}}</pre>
See Plunkr: https://plnkr.co/edit/MpSO21fIJq1DtJoXmE3V?p=preview

Related

Dynamically creating a form of checkboxes from nested JSON with ReactiveForms

I have a data structure which consists of a parent category such as music. its title is defines in the object under hobbies[0].parent.title I want to have a ngFor which outputs this title out (I have no issues here), then under that there will be another nested ngFor dealing with the sub-genres which is under the hobbies[0].children[i] as an array - I would like to output these as checkboxes which is again no problem but the issue arises when I try to make them a reactive form, my basic need is to validate that at least 1 or 2 have been checked and to get the info of the checked item, with ReactiveForms I have a good idea how to achieve this without a nested json struct, its really throwing me off
I have tried a ton of different approaches in these articles but I cant seem to get it to work, any help much appreciated.
https://stackblitz.com/edit/form-array-3-levels
https://medium.com/hashtaagco/3-levels-of-nested-form-arrays-including-reactive-validations-we-decided-to-go-inception-mode-on-4fffe667fb2a
https://www.toptal.com/angular-js/angular-4-forms-validation
https://jenniferwadella.com/blog/managing-dynamic-and-nested-forms-angular
Ive been playing around with this for a few hours by my html has stayed the same
<form [formGroup]="form" action="">
<label *ngFor="let parent of fetchInterests; let i = index">
<div *ngFor="let item of parent.children; let i = index">
<ion-checkbox ></ion-checkbox> {{ item.title }} {{ item.selected }}
</div>
</label>
</form>
my json demo data is below
var hobbies = [{
"parent": {
"parentId": "dMGkZuB8JV",
"title": "Music",
"hero": "https://s3-eu-west-1.amazonaws.com/music/placeholder.jpg"
},
"children": [{
"title": "Jazz",
"hero": "https://s3-eu-west-1.amazonaws.com/music/placeholder.jpg",
"about": "",
"selected": false
}, {
"title": "Rock",
"hero": "https://s3-eu-west-1.amazonaws.com/music/placeholder.jpg",
"about": "",
"selected": false
}, {
"title": "Classical",
"hero": "https://s3-eu-west-1.amazonaws.com/music/placeholder.jpg",
"about": "",
"selected": false
}, {
"title": "Soul",
"hero": "https://s3-eu-west-1.amazonaws.com/music/placeholder.jpg",
"about": "",
"selected": false
}]
}, {
"parent": {
"parentId": "19h2yOfZaq",
"title": "computers and systems",
"hero": "https://s3-eu-west-1.amazonaws.com/it/placeholder.jpg"
},
"children": [{
"title": "data processing",
"hero": "https://s3-eu-west-1.amazonaws.com/it/placeholder.jpg",
"about": "",
"selected": false
}]
}, {
"parent": {
"parentId": "m2zQkAgOog",
"title": "African planes",
"hero": "https://s3-eu-west-1.amazonaws.com/outdoors/placeholder.jpg"
},
"children": [{
"title": "camping",
"hero": "https://s3-eu-west-1.amazonaws.com/outdoors/placeholder.jpg",
"about": "",
"selected": false
}, {
"title": "swimming",
"hero": "https://s3-eu-west-1.amazonaws.com/outdoors/placeholder.jpg",
"about": "",
"selected": false
}, {
"title": "hunting",
"hero": "https://s3-eu-west-1.amazonaws.com/outdoors/placeholder.jpg",
"about": "",
"selected": false
}]
}]
Kravitz, first think about what do you want to change. You can planning change only selected, or all the object.
I supouse you want to change only the "check", so you need a formArray of formArrays
hobbiesForm: FormArray
ngOnInit() {
this.hobbiesForm = new FormArray(this.hobbies.map(x => new FormArray([], this.customValidator())))
//this give so formArray as hobbies you get
this.hobbies.forEach((x, index) => { //for each hobiee
const array=this.hobbiesForm.at(index) as FormArray
x.children.forEach(c=>{
array.push(new FormControl(c.selected))
})
})
}
It's a few complex manage a formArray of FormArrays, but you only need take account that when we make *ngFor="let control of formArray.controls", "control" is the element in the array. So in hobbiesForm.controls will be a FormArray and inside the formArray will be a control.
Well, better in code
<form [formGroup]="hobbiesForm" (submit)="submit(hobbiesForm)">
<!--array is the inner FormArray--->
<div *ngFor="let array of hobbiesForm.controls;let i=index">
<b>{{hobbies[i].parent.title}}:</b>
<!--control is the inner control of the array-->
<div *ngFor="let control of array.controls;let j=index">
<label>{{hobbies[i].children[j].title}}</label>
<input type="checkbox" [formControl]="control">
</div>
</div>
<button>submit</button>
</form>
Well, in submit we perhafs want to change the "hobbies" object, remember that we only has in value some like [[true,false,false,true],[false],[true,false,false]]
submit(form)
{
if (form.valid)
{
form.value.forEach((x,i)=>
{
x.forEach((c,j)=>{
this.hobbies[i].children[j].selected=c;
})
})
console.log(this.hobbies)
}
}
A customValidator can be like
customValidator() {
return (formArray: FormArray) => {
return formArray.value.filter(x=>x).length>0?null:{error:"at leat one"}
}
}
If is over all the form
customValidatorOverAll(min)
{
return (formArray: FormArray) => {
let count=0;
formArray.value.forEach(x=>{
count+=x?x.filter(c=>c).length:0;
})
return count>=min?null:{error:"At least select "+min}
}
}
you can see it in this stackblitz
Update well, this question is about ion-select. A ion-select multiple return an array with the values, so the things are very different. We are goin to obtain some like
[["Jazz","Rock"]["dat aprocessing"],["camping"]]
Thats an array of array but, we need an array of FormControls only. yes a FormControl can store any thing even an array. So
ngOnInit() {
this.hobbiesForm = new FormArray(
this.hobbies.map(x => new FormControl('', this.customValidator())))
}
And our .html some like
<form [formGroup]="hobbiesForm" (submit)="submit(hobbiesForm)">
<ion-item *ngFor="let control of hobbiesForm.controls;let i=index">
<ion-label>{{hobbies[i].parent.title}}:</ion-label>
<ion-select [formControl]="control" multiple="true" cancelText="cancel" okText="ok">
<ion-option *ngFor="let hobbiee of hobbies[i].children; let i = index"
[value]="hobbiee.title" selected="false">{{hobbiee.title}}</ion-option>
</ion-select>
</ion-item>
</form>
Again we iterate over hobbiesForm.controls. But in this case, the select gives us an array.
Well, I put a customValidator. If we want that at least was select one it's only that our customValidator was like
customValidator() {
return (formControl: FormControl) => {
return formControl.value && formControl.value.length>0?null:{error:"At least select one"}
}
}
If we want a customValidator over All the form, we can use some like
customValidatorOverAll(min)
{
return (formArray: FormArray) => {
let count=0;
formArray.value.forEach(x=>{
count+=x.length;
})
return count>=min?null:{error:"At least select "+min}
}
}
And when we create the formArray
this.hobbiesForm = new FormArray(this.hobbies.map(x => new FormControl(''))
,this.customValidatorOverAll(2))
The ion-select stackblitz
NOTE: See that we iterate over formArray.controls, not over hobbies object, and use two index i,j and hobbies object to show the "labels", but this don't belong to the formArray
Rather than putting children as a separate array you can mention inside parent.
var hobbies = [{
"parent": {
"parentId": "dMGkZuB8JV",
"title": "Music",
"hero": "https://s3-eu-west-1.amazonaws.com/music/placeholder.jpg"
"children": [{
"title": "Jazz",
"hero": "https://s3-eu-west-1.amazonaws.com/music/placeholder.jpg",
"about": "",
"selected": false
}, {
"title": "Rock",
"hero": "https://s3-eu-west-1.amazonaws.com/music/placeholder.jpg",
"about": "",
"selected": false
}, {
"title": "Classical",
"hero": "https://s3-eu-west-1.amazonaws.com/music/placeholder.jpg",
"about": "",
"selected": false
}, {
"title": "Soul",
"hero": "https://s3-eu-west-1.amazonaws.com/music/placeholder.jpg",
"about": "",
"selected": false
}]
}
}

How to combine two parts of single form in angular?

I am making angular application with angular dynamic form where i am loading data for dynamic form through JSON..
JSON has two parts such as part 1 and part 2,
jsonDataPart1: any = [
{
"elementType": "textbox",
"class": "col-12 col-md-4 col-sm-12",
"key": "project_name",
"label": "Project Name",
"type": "text",
"value": "",
"required": false,
"minlength": 3,
"maxlength": 20,
"order": 1
},
{
"elementType": "textbox",
"class": "col-12 col-md-4 col-sm-12",
"key": "project_desc",
"label": "Project Description",
"type": "text",
"value": "",
"required": true,
"order": 2
}
]
jsonDataPart2: any = [
{
"elementType": "textbox",
"class": "col-12 col-md-4 col-sm-12",
"key": "property_one",
"label": "Property One",
"type": "text",
"value": "",
"required": true,
"order": 3
},
{
"elementType": "textbox",
"class": "col-12 col-md-4 col-sm-12",
"key": "property_two",
"label": "Property Two",
"type": "text",
"value": "",
"required": true,
"order": 4
},
{
"elementType": "radio",
"class": "col-12 col-md-3 col-sm-12",
"key": "property_required",
"label": "Property Required",
"options": [
{
"key": "required",
"value": "Required"
},
{
"key": "not_required",
"value": "Not Required"
}
],
"order": 5
},
{
"elementType": "checkbox",
"class": "col-12 col-md-3 col-sm-12",
"key": "property_check",
"label": "Property Required",
"order": 6
}
]
And i am loading the JSON as separate part like,
ngOnInit() {
//Building form first part
this.questions = this.service.getQuestions(this.jsonDataPart1)
this.form = this.qcs.toFormGroup(this.questions);
//Building form second part
this.questionsPartTwo = this.service.getQuestions(this.jsonDataPart2)
this.formPartTwo = this.qcs.toFormGroupPartTwo(this.questionsPartTwo);
}
And HTML looks like,
<div>
<form (ngSubmit)="onSubmit()" [formGroup]="form">
<h4> <b> Form Part One begins from here </b> </h4>
<div *ngFor="let question of questions" class="form-row">
<ng-container>
<app-question [question]="question" [form]="form"></app-question>
</ng-container>
</div>
<h4> <b> Form Part Two begins from here </b> </h4>
<div *ngFor="let partTwo of questionsPartTwo">
<ng-container>
<app-question [question]="partTwo" [form]="formPartTwo"></app-question>
</ng-container>
</div>
<div class="form-row">
<button type="submit" [disabled]="!form.valid">Save</button>
</div>
</form> <br>
<pre>
{{form?.value|json}}
</pre>
</div>
I need to combine these two and need to get single output for the whole single form..
In my real application i am having two json data and loading each separately and assigning form so please dont break out the part one and part two json..
Let me stop the code here as its getting long you might get confused..
Here is a working stackblitz: https://stackblitz.com/edit/angular-x4a5b6-2rykpo
Just take a workaround dynamic-form.component.ts and dynamic-form.component.html You will get clear what i have done..
Kindly help me to load the splitted JSON to this.service.getQuestions and get two parts and join both in final output to submit..
If i am wrong in approach kindly help me to correct it as i am new in angular and dynamic form.. It needs to be in angular dynamic form and json loading only no change in it..
Help i am expecting is from combining both parts 1 and 2 while submitting form..
Please help me i am stucking for a long to come out of it..
Use Top level form and wrap your child form inside the parent form and use provider to use existing Form
Parent.component.html
<form [formGroup]="form" (ngSubmit)="onClick();">
<h1>Part1</h1>
<div class="row" formArrayName="part1" *ngFor="let c of form['controls']['part1']['controls'];let i =index">
<div class="col">
<input [attr.type]="jsonDataPart1[i].type"
class="form-control" [attr.placeholder]="jsonDataPart1[i].label"
[formControlName]="i" >
</div>
</div>
<app-part2 [part2]="jsonDataPart2">
<h1>Part2</h1>
</app-part2>
<br>
<button class="btn btn-primary">Save</button>
</form>
child.component.ts
import { Component, OnInit, Input } from '#angular/core';
import { FormGroup, FormControl, ControlContainer, FormGroupDirective, FormArray } from '#angular/forms';
#Component({
selector: 'app-part2',
templateUrl: './part2.component.html',
styleUrls: ['./part2.component.css'],
viewProviders: [{ provide: ControlContainer, useExisting: FormGroupDirective }]
})
export class Part2Component implements OnInit {
#Input('part2') part2;
part2Form;
constructor(private parentForm: FormGroupDirective) { }
ngOnInit() {
this.part2Form = this.parentForm.form;
const control = this.part2.map(d => new FormControl());
this.part2Form.addControl('part2F', new FormGroup({
part2: new FormArray(control)
}))
}
}
Example:https://stackblitz.com/edit/angular-xrrabj
You can take several aproach
1.-Create a formJoin that was {form1:questions of form1,form2:questions of form2}
In your ngOnInit
formJoin:FormGroup;
ngOnInit()
{
this.questions = this.service.getQuestions(this.jsonDataPart1)
this.form = this.qcs.toFormGroup(this.questions);
this.questionsPartTwo = this.service.getQuestions(this.jsonDataPart2)
this.formPartTwo = this.qcs.toFormGroup(this.questionsPartTwo);
this.formJoin=new FormGroup({form1:this.form,form2:this.formPartTwo})
}
//In your .html
<form (ngSubmit)="onSubmit()" [formGroup]="formJoin">
2.-Join the question in an unique json
this.allquestions=this.service.getQuestions(
this.jsonDataPart1.concat(this.jsonDataPart2));
this.formJoin2=this.qcs.toFormGroup(this.allquestions);
//get the first question.key in the second form
this.questionBreak=this.jsonDataPart2[0].key;
//In your .html
<form (ngSubmit)="onSubmit()" [formGroup]="formJoin2">
<h4> <b> An uniq Form </b> </h4>
<div *ngFor="let question of allquestions" class="form-row">
<!--make a break fro the secondForm-->
<ng-container *ngIf="question.key==questionBreak">
<h4> <b> Form Part Two begins from here </b> </h4>
</ng-container>
<ng-container>
<app-question [question]="question" [form]="formJoin2"></app-question>
</ng-container>
</div>
</form>
IMPORTANT NOTE: You needn't have two functions in service toFormGroup and toFormGroupPartTwo. If you see is the same function, you "feed" the function with different arguments, but is the same function.
In the stackblitz has the two options together
Update
update code to make a break,

Angular radio selection not checked correctly

In my Angular1.x app, my models maps to my radio selection correctly, however the radio button itself is not selected except for the very last one. Not really sure what the problem is. I created a very small example below to illustrate this behaviour. Any help is greatly appreciated.
http://plnkr.co/edit/dgGCvtOEb9WKTNtQHjqd?p=preview
angular.module('todoApp', [])
.controller('TodoListController', function() {
var todoList = this;
todoList.questions = [{
"category": "Movies",
"questions": [{
"title": "M1",
"score": "4",
},
{
"title": "M2",
"score": "2",
}
]
},
{
"category": "Foods",
"questions": [{
"title": "F1",
"score": "3",
},
{
"title": "F2",
"score": "4",
}
]
},
{
"category": "Sports",
"questions": [{
"title": "S1",
"score": "5",
}]
}
];
});
<!doctype html>
<html ng-app="todoApp">
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.1/angular.min.js"></script>
<script src="todo.js"></script>
<link rel="stylesheet" href="todo.css">
<link rel="stylesheet" href="http://netdna.bootstrapcdn.com/twitter-bootstrap/2.0.4/css/bootstrap-combined.min.css">
</head>
<body>
<h2>Todo</h2>
<div ng-controller="TodoListController as todoList">
<div ng-repeat="(ccKey, cc) in todoList.questions">
<hr/>
<div>
<b style="color: red;">Category</b> : {{cc.category}}
</div>
<div ng-repeat="(qqKey, qq) in cc.questions">
<br/>
{{qq.title}} : Selected Score: {{qq.score}}
<br/>
<div ng-repeat="n in [].constructor(5) track by $index">
<input type="radio" ng-model="qq.score" name="q-{{ccKey}}-{{qqKey}}" value="{{$index+1}}"><br/>Score: {{$index+1}} : Group: q-{{ccKey}}-{{qqKey}}
</div>
</div>
</div>
</div>
</body>
</html>
Try removing the name attribute from your <input type="radio"> tag. This seems to be causing a conflict with what Angular is doing to manage your radio tags. I am able to see all categories selected, and the selections are still grouped within a given question.
In the Angular input[radio] docs, they do not show using a name attribute: https://docs.angularjs.org/api/ng/input/input%5Bradio%5D
If what you want is the initial selection of the values when the page first loads,then use
ng-value="($index+1).toString()"
Since your scores are given as strings, you need to convert the number to string to match the score when you use "ng-value". Or better, you can just set your scores as integers and leave the rest of the code unchanged:
todoList.questions = [{
"category": "Movies",
"questions": [{
"title": "M1",
"score": 4,
},
{
"title": "M2",
"score": 2,
}
]
}, ....
Here is a modified plunker

Select tag not returning input data in correct form (Angular)

Using my json file, I'm making select type of questions on my web app.
The code I have for my select tag is like following:
<div class="form-group" ng-class="{ 'has-error': form.$submitted && form[field.id].$invalid }" ng-if="field.type === 'select'">
<label for="{{field.id}}">{{field.title}}</label>
<br>
<select ng-model="formData[field.id]" ng-value="{{value.title.id}}" ng-options="value as value.title for value in field.values">
<option disabled selected value> -- select an option -- </option></select>
<p class="form-group-note" ng-if="field.info" ng-bind="field.info"></p>
<div ng-show="form.$submitted" ng-cloack>
<span class="help-block" ng-show="form['{{field.id}}'].$error.required" ng-if="field.validations.required">Please enter a value, this field is required</span>
</div>
</div>
I have a json file like following:
{
"groups": [
{
"id": "10_8_group",
"title": "Existence",
"index": 60,
"part": 10,
"sections": [
{
"id": "10_8_section",
"title": "Existence",
"fields": [
{
"id": "10_8_labor_organization",
"title": "8. Does it exist?",
"info": "Always select \"No\"",
"type": "select",
"size": {
"width": 100,
"height": 1
},
"values": [
{
"id": 1,
"title": "Yes"
},
{
"id": 2,
"title": "No. If no, proceed to Part 9. and type or print your explanation."
}
]
}
]
}
]
}
]
}
when the data is being saved in localStorage(Angular's frontend model), it saves the data as "10_8_labor_organization":{"id":1,"title":"Yes"}}
However, I would like to save this as
"10_8_labor_organization":"Yes"
How can I accomplish this??
write your code like value.id as value.title for value in field.values

JsViews Checkbox binding from an inner loop

I am trying to get a two way binding for a checkbox in an inner loop using JsViews
Not sure if this is possible.
<div id="targetSelection"></div>
<script id="targetItem" type="text/x-jsrender">
<b>{^{:text}}</b>
<div id="answers_{^{:fieldName}}" class='collapse'>
{^{for answers ~fieldName=fieldName}}
<label>
<input type="checkbox" data-fieldName="{{>~fieldName}}" data-index="{{:index}}" data-link="{:selected}" checked="{^{:selected}}" /> {{:text}} : {^{:selected}}
</label>
<br /> {{/for}}
</div>
<br />
</script>
and the JS code:
var target = [{
"fieldName": "GENDER",
"text": "Gender",
"answers": [{
"index": 1,
"text": "Male"
}, {
"index": 2,
"text": "Female"
}, ]
}, {
"fieldName": "AGE",
"text": "Age",
"answers": [{
"index": 1,
"text": "15-19"
}, {
"index": 2,
"text": "20-24"
}, {
"index": 3,
"text": "25-29"
}, {
"index": 4,
"text": "30-34"
}, {
"index": 5,
"text": "35-39"
}, {
"index": 6,
"text": "40-44"
}, {
"index": 7,
"text": "45+"
}, ]
}];
$.each(target, function(questionIndex, question) {
$.each(question.answers, function(answerIndex, answer) {
answer.selected = true;
});
});
$("#targetSelection").html($.templates("#targetItem").render(target));
http://jsfiddle.net/22q7z9n9/
I am also trying to fire an event when the checkbox is changed
Thanks in advance
Did you check the JsViews docs? You are calling the render() method, not the link() method, and are using jsrender.js not jsviews.js!
So you need to load jsviews.js as in Example page, and then write: $.templates("#targetItem").link("#targetSelection", target);
See http://www.jsviews.com/#jsv-quickstart
Once you have read the basics, you can continue along the lines of:
{^{for answers}}
<label>
<input type="checkbox" data-link="selected" />
{{:text}} : {^{:selected}}
</label>
<br />
{{/for}}
For the event, there are a few options including to bind directly to the input change event, or listen to observable changes in your data etc. (http://www.jsviews.com/#observe). See the examples...
Here is a working version http://jsfiddle.net/28Lezc9m/4/.
I also changed: <div id="answers_{^{:fieldName}}" class='collapse'> to <div data-link="id{:'answers_ + fieldName" class='collapse'> - as explained in this tutorial sequence.

Categories