Avoid pushing duplicate objects to an angular array - arrays

I have multiple checkboxes in my angular application. When user checked and unchecked checkboxes I want to pass those true/false values into an array. It's happening from below code.
But my problem is as you can see the below console.log, it has duplicate checkbox values(index 0 and 3 have same thing) and push it to the array.
I want to know how to check duplicate objects and avoid pushing object to the array.
.ts file
layerChange(e:any){
var isChecked = e.target.checked;
var id = e.target.attributes.id.nodeValue;
const layer = {
isChecked: isChecked,
id: id,
}
this.layers.push(layer);
console.log(this.layers);
}
.html file
<input id="population" (change)="layerChange($event)" type="checkbox">
<input id="gender" (change)="layerChange($event)" type="checkbox">
<input id="householdIncome" (change)="layerChange($event)" type="checkbox">
console.log(this.layers)
**0: {isChecked: true, id: 'population'}**
1: {isChecked: true, id: 'age'}
2: {isChecked: false, id: 'population'}
**3: {isChecked: true, id: 'population'}**

You can check if an entry exists first using either :
var id = e.target.attributes.id.nodeValue;
var attr = this.layer.find(x => x.id === id);
if(!attr)
this.layers.push(layer);
or
this.layer.filter(x => x.id === id)
Edit
in your scenario is better to construct the array only one time in page load.
ngOnInit(): void {
this.layer = [{id: 'age', isChecked: false}, {id:'population',isChecked: false}]
}
and then alter the check state when user check/uncheck :-
layerChange(e:any){
var isChecked = e.target.checked;
var id = e.target.attributes.id.nodeValue;
this.layer.find(x => x.id === id).isChecked = isChecked;
console.log(this.layers);
}

You can create a string array which contains only ids and you can insert or remove elements from the array as per the selection
layerChange(e:any) {
const id = e.target.attributes.id.nodeValue;
const index = this.layers.findIndex(el => el === id)
if(index === -1) {
this.layers.push(id);
} else {
this.layers.splice(index, 1)
}
console.log(this.layers);
}

my GOD!
Kalana (and others) we need re-thinking the problem using "variables". Yes, Angular philosophy is binding variables. Variables in .ts makes the values are showed in the .html
So, some simple like declare an array of object in the .ts
layers:any[]=[{isChecked: true, id: 'population'},
{isChecked: true, id: 'age'},
{isChecked: false, id: 'population'}
]
Allow us write in .html
<ng-container *ngFor="let layer in layers">
<input type="checkbox" [(ngModel)]="layer.isChecked"
(change)="onChange()">
</ng-container>
In .ts we has a function:
onChange(){
console.log(this.layers)
}

Finally , I was able to find a solution. Thank you.
layerChange(e:any){
var isChecked = e.target.checked;
var id = e.target.attributes.id.nodeValue;
const index = this.layers.findIndex(el => el.id === id);
const layer = {
isChecked: isChecked,
id: id,
}
if(index > -1){
this.layers[index].isChecked = isChecked;
}else{
this.layers.push(layer);
}
console.log(this.layers);
}

Related

Push only checked and not duplicated checkbox values to an array in Angular?

I have three checkboxes and I want to create an array which should have the values of the checked checkboxes values and should avoid pushing duplicating values.
This is what I have tried but values are pushing even though it is checked or not.
Can someone let me know what is the issue here?
.ts
public dataSource: any[] = [];
compsForm: FormGroup = new FormGroup({
dataSource: new FormControl('', Validators.required),
name: new FormControl('',Validators.required),
});
dataSourceChange(event:any){
var name = event.target.name;
var isChecked = event.target.checked;
const index = this.dataSource.findIndex((el) => el.id === name);
if (index > -1) {
this.dataSource[index].isChecked = isChecked;
} else {
this.dataSource.push(name);
}
this.dataSource = [...this.dataSource];
console.log(this.dataSource);
}
.html
<form [formGroup]="compsForm">
<input type="checkbox" name="om" (change)="dataSourceChange($event)" formControlName = "dataSource"> <span> OM</span>
<input type="checkbox" name="fe" (change)="dataSourceChange($event)" formControlName = "dataSource"> <span> FE</span>
<input type="checkbox" name="be" (change)="dataSourceChange($event)" formControlName = "dataSource"> <span> BE </span>
</form>
Some things to consider:
The formGroup in your ts should match the "shape" of the form in the html.
If the html form has 3 inputs then so should the formGroup.
The html should have different names on each input.
You were only pushing elements in your array and not removing them when unchecked so the array will just keep getting bigger.
When you're worried about duplicate entries it is often better to work with a "Set" and the convert the set to an array when you have done all your work.
CHeck out this SO answer: HashSet Explanation
.html
<form [formGroup]="compsForm">
<input
type="checkbox"
name="om"
(change)="dataSourceChange($event)"
formControlName="dataSource1"
/>
<span> OM</span>
<input
type="checkbox"
name="fe"
(change)="dataSourceChange($event)"
formControlName="dataSource2"
/>
<span> FE</span>
<input
type="checkbox"
name="be"
(change)="dataSourceChange($event)"
formControlName="dataSource3"
/>
<span> BE </span>
</form>
.ts
// private _dataSource: any[] = [];
private _dataSet: Set<string> = new Set<string>();
private _dataArray = [];
compsForm: FormGroup = new FormGroup({
dataSource1: new FormControl(''),
dataSource2: new FormControl(''),
dataSource3: new FormControl(''),
});
dataSourceChange(event: any) {
console.log(event.target.name);
const name = event.target.name;
const isChecked = event.target.checked;
if (isChecked) this._dataSet.add(name);
else this._dataSet.delete(name);
this._dataArray = Array.from(this._dataSet);
console.log(this._dataArray);
}
the idea is from this SO (the part of the simple e.g. with a variable). You simply change the variable "answer" by the formControl.
<div *ngFor="let option of options">
<input
#check
type="checkbox"
[checked]="compsForm.get('dataSource').value &&
compsForm.get('dataSource').value.indexOf(option) >= 0"
(change)="onChange(option, check.checked)"
/>
{{ option }}
</div>
options=["check 1","check 2","check3"]
compsForm: FormGroup = new FormGroup({
dataSource: new FormControl('', Validators.required),
name: new FormControl('',Validators.required),
});
onChange(option:string,checked:boolean)
{
let answer=this.compsForm.get('dataSource').value || [];
if (checked && answer.indexOf(option)<0)
{
answer=[...answer,option]
.sort((a,b)=>this.options.indexOf(a)>this.options.indexOf(b)?1:-1)
//we can also not sort the array
//answer=[...answer,option]
}
if (!checked)
answer=answer.filter(x=>x!=option)
this.compsForm.get('dataSource').setValue(answer.length?answer:null)
}
the stackblitz
NOTE: If you want simply the code you can use a getter
get dataSourceValue(){
return this.compsForm.get('dataSource').value
}
Update options as array with value and label
If we has some like
options2 = [
{ value: 'check1', label: 'Check 1' },
{ value: 'check2', label: 'Check 2' },
{ value: 'check3', label: 'Check 3' },
];
We need change a few the code
<div *ngFor="let option of options2">
<input
#check
type="checkbox"
[checked]="compsForm2.get('dataSource').value &&
compsForm2.get('dataSource').value.indexOf(option.value) >= 0"
(change)="onChange2(option.value, check.checked)"
/>
{{ option.label }}
</div>
See how we pass to the function option.value and ask about .value.indexOf(option.value)
Our function change becomes
onChange2(option: string, checked: boolean) {
let answer = this.compsForm2.get('dataSource').value || [];
if (checked && answer.indexOf(option) < 0) {
//we use "optionsValue" for easy sort
const optionsValue = this.options2.map((x) => x.value);
answer = [...answer, option].sort((a, b) =>
optionsValue.indexOf(a) > optionsValue.indexOf(b) ? 1 : -1
);
//we can also not sort the array
//answer=[...answer,option]
}
if (!checked) answer = answer.filter((x) => x != option);
this.compsForm2.get('dataSource').setValue(answer.length ? answer : null);
this.compsForm2.get('dataSource').markAsTouched();
}

Remove item from state

I know this question has been asked a lot but I haven't seem to find a solution even tho I've tried different scenarios.
this.state = {
chartTypes: [{name: 'Bar', id: 1},{name: 'Line', id: 2},{name: 'Pie', id: 3},{name: 'Radar', id: 4} ],
selectedChart: [],
}
onSelect = (selectedList, selectedItem) => {
// selectedItem.name returns name of chartTypes, selectedItem.id returns id of chartTypes.
this.setState({
selectedChart: selectedItem.name
})
}
onRemove = (selectedList, removedItem) => {
// removedItem.name returns name of chartTypes, removedItem.id returns id of chartTypes.
}
The select option works fine but I just put it there so you can have a better understanding. onSelect puts every selectedItem.name into selectedChart. On the remove function, how may I remove item from this.state.selectedChart based on the value/index.
I think you can do something like this
let temp = this.state.selectedChart;
temp=temp.filter((item) => item !== removedItem.name);
this.setState({ selectedChart: temp });
var newArray = this.state.selectedChart.filter((el)=>
el.id !==removedItem.id
);
after filter
this.setState({selectedChart:newArray})
Just filter and set backed to state
onRemove = (selectedList, removedItem) => {
let filtered = this.state.chartTypes.filter(list=> list.id !==removedItem.id);
this.setState({ chartTypes: filtered });
}

Adding Selected Option Prices for Checkboxes - Using Svelte

I'm trying to get update the total as the checkboxes are selected and unselected (incase the user changes their mind and no longer wants the item). But I'm not sure how to get the actual value I have assigned to each toy. For example: If the user selects toy 1 and 3 they should see: You ordered Toy1, Toy3 and your total is $6.00. For now I assigned the values with the names themselves which isn't what I want but I just put that to show what I'm trying to do. Is their something else I should be using actually perform an operation to get the total?
<script>
let toylist = [];
let toynames = [
'Toy1 5.00',
'Toy2 5.00',
'Toy3 1.00',
];
function join(toylist) {
return toylist.join(', ');
}
</script>
{#each toynames as toy}
<label>
<input type=checkbox bind:group={toylist} value={toy}> {toy}
</label>
{/each}
{#if toylist.length === 0}
<p>Pick at least one toy</p>
{:else}
<p>
You ordered {toylist.join(', ')} and your total is
</p>
{/if}
Ok, first you should separate the toynames array into an array of names and values. Then you should bind to the checked property of the input.
In order to display the current state to the user, we need a reactive declaration. Let's call it total. It contains two functions. The first one gives back an array of the selected names, the second the sum of the selected values.
Both work by looking at the selected property of the object in the toylist array. This updates due to our binding of the checked attribute. I created a repl for you to toy ;-) around with
<script>
let toylist = [
{name: "Toy", value: 5, selected: false},
{name: "Elephant", value: 6, selected: false},
{name: "Choo-Choo", value: 1, selected: false}
]
$: total = {
names: toylist.reduce((acc, cV) => {
if (cV && cV.selected) {
return [...acc, cV.name];
} else return [...acc];
}, []),
values: toylist.reduce((acc, cV) => {
if (cV && cV.selected) {
return parseInt(acc) + parseInt(cV.value);
} else return parseInt(acc);
}, 0)
};
</script>
{#each toylist as {name,value, selected}}
<label>
<input type=checkbox bind:checked={selected} bind:value={value}> {name}
</label>
{/each}
{#if toylist.length === 0}
<p>Pick at least one toy</p>
{:else}
<p>
You ordered {total.names.length < 1 ? "nothing": total.names} and your total is {total.values}
</p>
{/if}
EDIT:
Here is the total function with a more classic syntax:
$: total = {
names: toylist.reduce(function(acc, cV) {
if (cV && cV.selected) {
return [...acc, cV.name];
} else return [...acc];
}, []),
values: toylist.reduce(function(acc, cV) {
if (cV && cV.selected) {
return parseInt(acc) + parseInt(cV.value);
} else return parseInt(acc);
}, 0)
};
And here without the ? operator:
<script>
function renderNames() {
if (total.names.length < 1) {
return "nothing";
} else {
return total.names;
}
}
</script>
<p>You ordered {renderNames()} and your total is {total.values}</p>
The best way to isolate the price of each toy would be to make your array of toys into an array of objects where 'name' is one key value pair and price another. For manipulating the data it would be helpful if each toy had an id, and I've added a 'selected' boolean value to each toy that is updated if they are added or removed from the "toylist". I've also added a 'total' variable to hold the total of selected toys prices.
I have played with your code a bit to make this work. I have used buttons instead of checkboxes but you could do it in any way you like. So give this code a go and it should be doing what you want.
<script>
let toylist = [];
let toys = [
{id: 1, name: 'Toy 1', price: 5.00, selected: false},
{id: 2, name: 'Toy 2', price: 5.00, selected: false},
{id: 3, name: 'Toy 3', price: 1.00, selected: false}
];
let total = 0.00
function join(toy) {
if(toy.selected === false) {
toylist = [...toylist, toy.name]
total = total+toy.price
let i = toys.findIndex(t => t.id === toy.id)
toys[i].selected = true
toys = toys
} else {
total = total-toy.price
let i = toys.findIndex(t => t.id === toy.id)
let i2 = toylist.findIndex(t => t === toy.name)
toylist.splice(i2, 1)
toylist = toylist
toys[i].selected = false
toys = toys
}
}
</script>
{#each toys as toy}
<label>
{toy.name}: ${toy.price} <button on:click="{() => join(toy)}" value={toy.name}>{toy.selected ? 'remove' : 'add'}</button>
</label>
{/each}
{#if toylist.length === 0}
<p>Pick at least one toy</p>
{:else}
<p>
You ordered {toylist} and your total is ${total}
</p>
{/if}

How can I return true if a specific object was found in an array?

I have a simple form with checkboxes made in Angular and I would like to make the checkbox check itself if the user has this role.
Here is what I tried:
<form [formGroup]="rolesForm">
<label
formArrayName="roles"
*ngFor="let role of rolesForm.controls['roles'].controls; let i = index"
>
<input
type="checkbox"
[checked]="checkIfTrue(role[i])"
[formControlName]="i"
/> {{role[i].name}}
</label>
</form>
The component itself:
roles: Role[] = [
{
uid: '456DNC',
name: 'Admin'
},
{
uid: '546DKZ',
name: 'Member'
},
{
uid: '741JXY',
name: 'Guest'
}
]
user: User = {
uid: '123ABC',
name: 'Steve',
roles: [
{
uid: '456DNC',
name: 'Admin'
}
]
}
rolesForm: FormGroup;
// So I can get every roles and the user can check multiple checkboxes to get as many roles as it wants
ngOnChanges() {
const formControls = this.roles.map(role => new FormControl(false));
this.rolesForm = this.formBuilder.group({
roles: new FormArray(formControls)
});
}
checkIfTrue(role: Role) {
// I assume that it should return true if it finds the role in the user.roles array but it doesn't. It doesn't check the box and I get an eror.
return this.user.roles.find(role);
}
I'm getting something like this: [object Object] is not a function at Array.find
I have tried the following functions .indexOf() and .includes()
You should set the value when building the form instead of using the [checked] property, because of the reactive form you use.
https://stackblitz.com/edit/angular-hsvug5?file=src/app/app.component.ts
ngOnChanges() {
const formControls = this.roles.map(role => new FormControl(this.checkIfTrue(role)));
this.rolesForm = this.formBuilder.group({
roles: new FormArray(formControls)
});
}
checkIfTrue(role) {
return this.user.roles.find(r => r.uid === role.uid);
}
<form [formGroup]="rolesForm">
<label
formArrayName="roles"
*ngFor="let role of rolesForm.controls['roles'].controls; let i = index"
>
<input
type="checkbox"
[formControlName]="i"
/> {{roles[i].name}}
</label>
</form>
indexOf() return -1 if it does not find elements.
So you can implement something like this:
return this.user.roles.indexOf(role) != -1
In this way you'll have true if indexOf found an element false otherwise.
in your checkIfTrue(role: Role) method
Everything is fine only change its like that :
checkIfTrue(role: Role) {
this.user.roles.find(e=>{
if(e.name === role) {
return true;
}
});
}

How Can I Make All Checkboxes "Checked" By Default

How can I ensure by default all by checkboxes (output from my data) are all checked by default on load?
Output checkbox
<div v-for="(category, index) in remove_category_duplicates" class="form-check">
<input type="checkbox" class="form-check-input" v-model="cat_data" :id="category" :value="category">
<label class="form-check-label">{{ category }}</label>
</div>
Setup checkbox values from data
remove_category_duplicates: function () {
// Get all categories and remove duplicates
let data = {}
this.info.forEach(i=>{
Object.assign(data,i.category_data);
})
return data;
},
Data:
{
"id": 1,
"title": "Title one",
"category_data": {
"2": "Team",
"7": "Queries"
}
},
CodePen: https://codepen.io/anon/pen/XxNORW?editors=1011
Thanks
To initialize the checkboxes to true/checked, their v-model array (cat_data) should contain the true-value of each checkbox. In this case, it would be:
["Team", "Questions", "Queries", "Fax"]
Here's how I would update your code:
Add a computed property to return an array of available categories:
computed: {
categories() {
const cats = this.remove_category_duplicates;
return Object.keys(cats).map(k => cats[k]);
}
}
Update select() to set cat_data based on selectAll. If selectAll is true, set cat_data to the category array computed above (thus marking all boxes checked), or else an empty array (thus unchecking all boxes):
methods: {
select() {
this.cat_data = this.selectAll ? this.categories : [];
}
}
Add a method (e.g., named "toggleSelectAll") to toggle selectAll based on whether all checkboxes are checked, keeping the Select All checkbox in sync with the state of the other checkboxes:
methods: {
toggleSelectAll(e) {
const checked = e.currentTarget.checked;
if (checked) {
this.selectAll = this.arrayContains(this.categories, this.cat_data);
} else {
this.selectAll = false;
}
}
}
Add a change-handler on each checkbox (except the Select All box) that calls toggleSelectAll defined above:
<div v-for="category in remove_category_duplicates">
<input type="checkbox" #change="toggleSelectAll">
demo

Resources