How to add multiple selected checkbox values to an array using Javascript? - javascript

I have an input element being populated using the *ngFor loop fetching the data from another array. On selecting multiple checkboxes, I need their values to be pushed into my empty array 'selectedArr'.
Find below the code:
import { Component } from "#angular/core";
#Component({
selector: "app-root",
templateUrl: "./app.component.html",
styleUrls: ["./app.component.css"]
})
export class AppComponent {
title = "CodeSandbox";
toDo = ["Test", "Eat", "Sleep"];
task: string;
addTask(task: string) {
this.toDo.push(task);
}
selectedArr = [];
deleteValue() {}
addSelected(i) {
let checkId = document.getElementsByClassName("i");
console.log(checkId);
if (checkId.checked === true) {
this.selectedArr.push(i);
}
console.log(this.selectedArr);
}
}
<div>
<div class="form-group">
<label>Add a Task: </label>
<input class="form-control" type="text" [(ngModel)]="task" />
</div>
<button (click)="addTask(task)">Add</button>
<br />
<br />
<div>
My To Do List:
<ul>
<li *ngFor="let todo of toDo, index as i">
<input class="i" type="checkbox" (click)="addSelected(i)" />
{{todo}}
</li>
</ul>
</div>
<div class="btn class">
<button class="btn btn-primary" (click)="deleteValue()">Delete</button>
</div>
</div>

Try like this:
.html
<li *ngFor="let todo of toDo, index as i">
<input class="i" type="checkbox" [(ngModel)]="checked[i]" (ngModelChange)="addSelected(todo,$event)" />
{{todo}}
</li>
.ts
checked = []
selectedArr = [];
addSelected(item,evt) {
if (evt) {
this.selectedArr.push(item);
}else {
let i = this.selectedArr.indexOf(item)
this.selectedArr.splice(i,1)
}
}
Working Demo

The getElementsByClassName method of Document interface returns an array-like object of all child elements which have all of the given class name(s). Since you are passing the index, you can access the clicked element like:
addSelected(i) {
let checkId = document.getElementsByClassName("i")[i];
console.log(checkId);
if (checkId.checked) {
this.selectedArr.push(i);
} else {
// Remove the index from selectedArr if checkbox was unchecked
let idx = this.selectedArr.indexOf(i)
if (idx > -1) this.selectedArr.splice(idx, 1)
}
console.log(this.selectedArr);
}

please, the things easy works easy. You needn't actually manually the array. You should use a function (*)
get selectedArray()
{
return this.toDo.filter((x,index)=>this.checked[index])
}
<li *ngFor="let todo of toDo, index as i">
<!--remove (ngModelChange) -->
<input class="i" type="checkbox" [(ngModel)]="checked[i]" />
{{todo}}
</li>
{{selectedArray}}
(*) this allow you "start" the app with some selected

Related

How to properly store and use data from ng Form

This is my code so far:
My component.html:
<div class="container">
<Form #a ="ngForm" ngForm (ngSubmit)="onSubmit(a)">
<div *ngFor="let question of questions">
<div class = "form-group">
<a>{{question.questionId}}. {{question.question}}</a><br>
<input type="radio" ngModel name="Ans{{question.questionId}}" value="A" >
<label for="html">A. {{question.optionsA}}</label><br>
<input type="radio" ngModel name="Ans{{question.questionId}}" value="B" >
<label for="html">B. {{question.optionsB}}</label><br>
<input type="radio" ngModel name="Ans{{question.questionId}}" value="C" >
<label for="html">C. {{question.optionsC}}</label><br>
<input type="radio" ngModel name="Ans{{question.questionId}}" value="D" >
<label for="html">D. {{question.optionsD}}</label><br>
</div>
</div>
<button class="btn btn-primary " type="submit" >Submit</button>
</Form>
<div *ngIf="results.length >0">
<table>
<thead>
<th>Question No.</th>
<th>Correct Answer</th>
<th>Your Answer</th>
</thead>
<tbody *ngFor="let question of questions">
<td>{{question.questionId}}</td>
<td>{{question.answer}}</td>
</tbody>
</table>
{{score}}
{{results.length}}
</div>
</div>
and my component.ts:
import { Component, OnInit } from '#angular/core';
import { Quiz1 } from 'src/app/models/quiz1.model';
import { Quiz1Service } from 'src/app/services/quiz1.service';
import {FormControl, FormGroup, NgForm} from '#angular/forms';
#Component({
selector: 'app-quiz1',
templateUrl: './quiz1.component.html',
styleUrls: ['./quiz1.component.css']
})
export class Quiz1Component implements OnInit {
questions?: Quiz1[];
currentQuestion: Quiz1 = {};
currentIndex = -1;
score = 0;
results:String[] = [];
//index?: String[] = ['Ans1','Ans2'];
constructor(private quiz1Service: Quiz1Service) { }
ngOnInit(): void {
this.retrieveQuestions();
}
retrieveQuestions(): void {
this.quiz1Service.getAll()
.subscribe({
next: (data: any) => {
this.questions = data;
console.log(data);
},
error: (e: any) => console.error(e)
});
}
onSubmit(a:NgForm){
this.results?.push(a.value);
console.log(this.results);
console.log(this.results.length)
this.questions?.forEach((questions) => {
console.log(questions.questionId);
if(questions.answer==this.results[questions.questionId-1]){
this.score++
console.log(this.score)
}
});
}
}
When I print the results array in the console, this is what I got:
Array(1)
0: {Ans1: 'A', Ans2: 'C'}
length: 1
[[Prototype]]: Array(0)
as you can see, it is storing all the data in a single index and with the field name which won't do at all.
What I want is just the field data stored in different index in the array so I can use that.
Is that possible?
I don't think you can achieve what you want using template-driven forms.
You would have to use reactive forms and FormArray.

angular reactive form add FormArray inside a nested FormGroup inside another FormArray inside another FormGroup

I've created the nested FormGroup (questionnaire) which contains a FormArray (section) which contains another FormGroup (creatSection()) that contains another FormArray (question) in which we have another formgroup (creatQuestion()) in which we have another formarray(answer) that has a formgroup (creatAnswer())
Everything appears fine unless I want to add another question or another answer nothing the addSection works fine
so the creation is ok but the problem in the add.
what I'm thinking about is when you create a question it doesn't know to where it's going to be put in but I couldn't figure out how to fix it
my questionnaire.ts looks like this
import { Component, OnInit } from '#angular/core';
import { FormBuilder, FormGroup, FormArray } from '#angular/forms'
#Component({
selector: 'app-questionnaire',
templateUrl: './questionnaire.component.html',
styleUrls: ['./questionnaire.component.css']
})
export class QuestionnaireComponent implements OnInit {
questionnaire:FormGroup;
section:FormArray;
question:FormArray;
answer:FormArray;
constructor(private fb:FormBuilder) { }
ngOnInit() {
this.questionnaire=this.fb.group({
questionnaireName: [''],
section : this.fb.array([this.creatSection()])
})
}
creatSection():FormGroup{
return this.fb.group({
sectionName:[''],
question:this.fb.array([this.creatQuestion()])
})
}
addSection(){
this.section=this.questionnaire.get('section') as FormArray;
this.section.push(this.creatSection());
}
creatQuestion():FormGroup{
return this.fb.group({
questionName:[''],
type:[''],
answer:this.fb.array([this.creatAnswer()])
})
}
addQuestion(){
this.question=this.creatSection().get('question') as FormArray;
this.question.push(this.creatQuestion());
}
creatAnswer():FormGroup{
return this.fb.group({
id:[''],
answerName:['']
})
}
addAnswer(){
this.answer=this.creatQuestion().get('answer') as FormArray;
this.answer.push(this.creatAnswer());
}
onSubmit(){
console.log(this.questionnaire.value);
}
}
and the questionnaire.html is like this
<h1>questionnaire bla bla</h1>
<!-- <app-section></app-section> -->
<form [formGroup]="questionnaire" (ngSubmit)="onSubmit()">
<input type="text" placeholder="questionnaire..." formControlName="questionnaireName">
<ng-container formArrayName="section">
<div [formGroupName]="i" *ngFor="let ques of questionnaire.get('section').controls;let i = index;">
<input placeholder="section..." formControlName="sectionName" type="text">
<button (click)="addSection()">add section</button>
<ng-container formArrayName="question">
<div [formGroupName]="j" *ngFor="let sec of creatSection().get('question').controls;let j=index;">
<input placeholder="question..." formControlName="questionName" type="text">
<input placeholder="type..." formControlName="type" type="text">
<button (click)="addQuestion()">add question</button>
<ng-container formArrayName="answer">
<div [formGroupName]="k" *ngFor="let ans of creatQuestion().get('answer').controls;let k=index;">
<input placeholder="réponse..." formControlName ="answerName" type="text">
<button (click)="addAnswer()">add answer</button>
</div>
</ng-container>
</div>
</ng-container>
</div>
</ng-container>
<button type="submit">submit</button>
</form>
{{questionnaire.value | json}}
First Don't call to creatSection() in the .html
//WRONG
<div [formGroupName]="j" *ngFor="let sec of creatSection().get('question').controls;let j=index;">
//OK
<div [formGroupName]="j" *ngFor="let sec of ques.get('question').controls;let j=index;">
Well you need pass as argument the "indexs" of the arrays (I called "clasic way"), but you can also pass the group of the array itself
<button (click)="addAnswer(i,j)">add answer</button>
//or
<button (click)="addAnswer(sec)">add answer</button>
In a "clasic"
addAnswer(i:number:j:number)
{
const arraySection=((this.questionnaire.get('section') as FormArray)
const groupQuestion=(arraySection.get('question') as FormArray).at(i)
const arrayAnswer=groupQuestion.get('answer') as FormArray
arrayAnswer.push(this.creatAnswer())
}
If you pass the array itself
addAnswer(groupQuestion:FormArray)
{
const arrayAnswer=groupQuestion.get('answer') as FormArray
arrayAnswer.push(this.creatAnswer())
}
NOTE: with a stackblitz was easer check and it's possible eror in my code, please get it the idea
thanks to #Eliseo the result would look like
questionnaire.html
<h1>questionnaire bla bla</h1>
<form [formGroup]="questionnaire" (ngSubmit)="onSubmit()">
<input type="text" placeholder="questionnaire..." formControlName="questionnaireName">
<ng-container formArrayName="section">
<div [formGroupName]="i" *ngFor="let ques of questionnaire.get('section').controls;let i = index;">
<input placeholder="section..." formControlName="sectionName" type="text">
<button (click)="addSection()">add section</button>
<ng-container formArrayName="question">
<div [formGroupName]="j" *ngFor="let sec of ques.get('question').controls;let j=index;">
<input placeholder="question..." formControlName="questionName" type="text">
<input placeholder="type..." formControlName="type" type="text">
<button (click)="addQuestion(ques)">add question</button>
<ng-container formArrayName="answer">
<div [formGroupName]="k" *ngFor="let ans of sec.get('answer').controls;let k=index;">
<input placeholder="réponse..." formControlName ="answerName" type="text">
<button (click)="addAnswer(sec)">add answer</button>
</div>
</ng-container>
</div>
</ng-container>
</div>
</ng-container>
<button type="submit">submit</button>
</form>
{{questionnaire.value | json}}
and questionnaire.ts would look like this
import { Component,OnInit } from '#angular/core';
import { FormBuilder, FormGroup, FormArray } from '#angular/forms'
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent implements OnInit {
questionnaire:FormGroup;
section:FormArray;
question:FormArray;
answer:FormArray;
constructor(private fb:FormBuilder) { }
ngOnInit() {
this.questionnaire=this.fb.group({
questionnaireName: [''],
section : this.fb.array([this.creatSection()])
})
}
creatSection():FormGroup{
return this.fb.group({
sectionName:[''],
question:this.fb.array([this.creatQuestion()])
})
}
addSection(){
this.section=this.questionnaire.get('section') as FormArray;
this.section.push(this.creatSection());
}
creatQuestion():FormGroup{
return this.fb.group({
questionName:[''],
type:[''],
answer:this.fb.array([this.creatAnswer()])
})
}
addQuestion(groupSection:FormArray){
const arrayQuestion=groupSection.get('question') as FormArray
arrayQuestion.push(this.creatQuestion())
}
creatAnswer():FormGroup{
return this.fb.group({
id:[''],
answerName:['']
})
}
addAnswer(groupQuestion:FormArray){
const arrayAnswer=groupQuestion.get('answer') as FormArray
arrayAnswer.push(this.creatAnswer())
}
onSubmit(){
console.log(this.questionnaire.value);
}
}

Unable to render collection dynamically set at run time

I have a piece of JSON which represents some actions and parameters a user can set.
I want to display the actions in a dropdown, and when a user selects one - the required parameters associated with the action are displayed.
Example:
User selects an action with a single parameter, a single input box is displayed
User selects an action with two parameters, two input boxes are displayed.
I nearly have it working but the *ngFor isn't displaying the inputs for the selected action:
In onChange - if I print this.steps, I can see that this.steps[i].SelectedAction.UIParameters has a value so I'm not sure why it isn't being rendered.
JSON:
[
{
"ActionEnum": "CLICKELEMENT",
"Name": "Click Element",
"UIParameters": [
{
"ParameterEnum": "ELEMENTID",
"Description": "The id of the element to click"
}
]
},
{
"ActionEnum": "INPUTTEXT",
"Name": "Input Text",
"Description": "Enters text into the element identified by it's id",
"UIParameters": [
{
"ParameterEnum": "ELEMENTID",
"Description": "The id of the element"
},
{
"ParameterEnum": "TEXTVALUE",
"Description": "The text to enter into the element"
}
]
}
]
Typescript:
import { Component, Output, EventEmitter, OnInit } from "#angular/core";
import { ActionService } from "../services/action-service";
import { Action } from "../models/Action";
#Component({
selector: 'app-scenario-step-editor-component',
template: `
<form #formRef="ngForm">
<div *ngFor="let step of steps; let in=index" class="col-sm-3">
<div class="form-group">
<label class="sbw_light">Action:</label><br />
<select (change)='onChange()' [(ngModel)]="step.Action" name="action_name_{{in}}">
<option *ngFor="let action of this.availableActions" [(ngModel)]="steps[in].value" name="action_name_{{in}}" class="form-control" required>
{{action.Name}}
</option>
</select>
<div *ngIf="steps[in].SelectedAction">
<label class="sbw_light">Parameters:</label><br />
<ng-template *ngFor="let parameter of steps[in].SelectedAction.UIParameters">
<label class="sbw_light">{{parameter.ParameterEnum}}</label><br />
<input (change)='onChange()' type="text" [(ngModel)]="steps[in].Parameters" name="parameter_name_{{in}}" class="form-control" #name="ngModel" required />
</ng-template>
</div>
</div>
</div>
<button id="addStepBtn" type="button" class="btn btn-light" (click)="addScenarioStep()">Add Scenario Step +</button>
</form>`
})
export class ScenarioStepEditorComponent implements OnInit {
#Output() onSelectValue = new EventEmitter<{stepInputs: any[]}>();
steps = [];
availableActions: Action[];
constructor(private actionService: ActionService) {}
ngOnInit(): void {
this.actionService.list().subscribe( result => {
this.availableActions = result;
},
error => console.log('Error getting actions...') );
}
/* When user picks an option, save the chosen action with the rest of the parameters*/
onChange() {
for (let i = 0; i < this.steps.length; i++) {
let actionIndex = this.availableActions.findIndex(a => a.Name === this.steps[i].Action);
this.steps[i].SelectedAction = this.availableActions[actionIndex];
}
this.onSelectValue.emit( {stepInputs: this.steps} );
}
addScenarioStep() {
this.steps.push({value: ''});
}
}
<ng-template *ngFor="let parameter of steps[in].SelectedAction.UIParameters">
<label class="sbw_light">{{parameter.ParameterEnum}}</label><br />
<input (change)='onChange()' type="text" [(ngModel)]="steps[in].Parameters" name="parameter_name_{{in}}" class="form-control" #name="ngModel" required />
</ng-template>
Just replace ng-template with ng-container:
<ng-container *ngFor="let parameter of steps[in].SelectedAction.UIParameters">
<label class="sbw_light">{{parameter.ParameterEnum}}</label><br />
<input (change)='onChange()' type="text" [(ngModel)]="steps[in].Parameters" name="parameter_name_{{in}}" class="form-control" #name="ngModel" required />
</ng-container>
Reasons:
ng-container was suitable for that situation. It just holds "something" and can be iterated.
ng-template defines a template. You didn't need a template here, templates are not meant to be used for that. It could work, of course, but it's not suitable for your scenario.
Read more about ng-template and ng-container here: https://blog.angular-university.io/angular-ng-template-ng-container-ngtemplateoutlet/
As a final side note, you could use ng-template by defining an item and you could use ng-container with *ngTemplateOutlet to render the template. Check the above guide for some examples.

Check and Uncheck checkbox based on the value in React Redux Store

I am working on a React project, and have redux store configured.
Inside the redux store, there is cuisines array.
Each checkbox will have a value attribute with the unique cuisine id and if the checkbox is selected this value is stored in the redux store like this
cuisines: ["58bee38510f8981b04b379f2", "58bee38510f8981b04b37sd3]
Here is my component code which prints the checkboxes and at the same time it checks whether the checkbox value exists in redux store or not.
If value exist then checkbox must be selected.
import React from 'react';
import {connect} from 'react-redux';
import {loadJsLibraries} from './../../utils/LoadLibrary';
class Filters extends React.Component {
state = {
isChecked: false
}
cuisineStatus = (id) => {
const cuisineFound = this.props.filters.cuisines.find((cuisineId) => {
return cuisineId === id;
});
return cuisineFound ? true: false;
}
handleCheckBoxChange = (e) => {
let isChecked = e.target.checked;
alert(isChecked);
}
render() {
return (
<div className="col-md-3">
<div id="filters_col">
<a
data-toggle="collapse"
href="#collapseFilters"
aria-expanded="true"
aria-controls="collapseFilters"
id="filters_col_bt
">
Filters
<i className="icon-plus-1 pull-right"></i>
</a>
<div className="collapse" id="collapseFilters">
<div className="filter_type">
<h6>Distance</h6>
<input type="text" id="range" value="" name="range"/>
<input type="text" id="range-test" value="" />
<h6>Food Type</h6>
<ul>
<li><label><input type="checkbox" className="icheck" />Veg Only</label></li>
</ul>
<h6>Outlet Type</h6>
<ul>
<li><label><input type="checkbox" className="icheck" />Restaurant</label></li>
<li><label><input type="checkbox" className="icheck" />Food Court</label></li>
</ul>
</div>
<div className="filter_type">
<h6>Cuisines</h6>
<ul className="nomargin">
{
this.props.cuisines.map((cuisine) => {
return (<li>
<label className="checkbox-container">
{cuisine.name}
<input
type="checkbox"
value={cuisine.id}
className="cuisineCheckbox"
onChange={(e) => this.handleCheckBoxChange(e)}
checked={this.cuisineStatus(cuisine.id}
/>
<span className="checkmark"></span>
</label>
</li>)
})
}
{loadJsLibraries()}
</ul>
</div>
</div>
</div>
</div>
);
}
}
export default connect(state => state)(Filters);
Inside the React component, there is function which will check whether the checkbox value exists in redux store or not.
If value exist in redux store, then functon returns true or else false.
This works perfectly fine, but the problem is that when I click on the checkbox, it fires the event but the check is not toggling.
Update
I tried this also
<input
type="checkbox"
value={cuisine.id}
className="cuisineCheckbox"
onChange={(e) => this.handleCheckBoxChange(e)}
{...(this.cuisineStatus(cuisine.id) ? {checked : true} : {})}
/>
But if the checkbox is checked then I am not able to uncheck it again.

AngularJS todo example delete row after pushing/removing from array

I have two separate containers: one for outstanding tasks and one for completed tasks.
<li ng-repeat="(key, task) in requirements.tasks.outstanding track by $index">
<span class="handle"> <label class="checkbox">
<input type="checkbox" name="checkbox-inline" ng-model="completionOutstanding" ng-checked="task.completed" ng-change="taskChange(task, key)">
<i></i> </label> </span>
<p>
{{task.name}} <br />
<span class="text-muted">{{task.description}}</span>
<span class="date">{{task.date_entered}}</span>
</p>
</li>
Markup augmented with Angular for completed tasks:
<li class="complete" ng-repeat="(key, task) in requirements.tasks.completed track by $index">
<span class="handle"> <label class="checkbox">
<input type="checkbox" name="checkbox-inline" ng-model="completionCompleted" ng-checked="task.completed" ng-change="taskChange(task, key)">
<i></i> </label> </span>
<p>
{{task.name}} <br />
<span class="text-muted">{{task.description}} </span>
<span class="date">{{task.date_entered}}</span>
</p>
</li>
Angular Controller taskChange function used:
$scope.taskChange = function(task, key) {
if(task.completed == false) {
task.completed = true;
$scope.requirements.tasks.completed.push(task);
delete $scope.requirements.tasks.outstanding[key];
} else {
task.completed = false;
$scope.requirements.tasks.outstanding.push(task);
delete $scope.requirements.tasks.completed[key];
}
}
The functionality is working as it should, however, the row in the actual view persists. The row along with the data should be removed, but when this is tested the row remains there. As I add/remove tasks, many rows are accrued.
How can I add and remove the HTML rows in the list while adding/removing my data?
JSON Schema:
{
"tasks": {
"outstanding":[
{}, {}, {}, {}
],
"completed":[
{}, {}, {}, {}
]
}
}
You're confusing Arrays and Objects in your code:
For an object use:
<!-- repeat attr -->
ng-repeat="(key, value) in items"
// add an item
items[key] = value;
// remove an item
delete items[key];
For an array:
<!-- repeat attr -->
ng-repeat="item in items"
// add an item
items.push(item);
// remove an item
items.slice(items.indexOf(item), 1);
I think the problem is you are using delete to remove item from old list, that will not remove the "slot" of the item in the array, just set it to undefined
e.g. [a, b, c], after delete b, it becomes [a, undefined, c]
You may want to try splice to remove the item from array

Categories