How to do pop operation in javascript when unchecked checkbox? - javascript

I have just the two checkbox whose codes looks like:
<div *ngFor="let item of documents">
<label>
<input type="checkbox" value="{{item.docId}}" [(ngModel)]="item.checked" [name]="item.docName"
(change)="editPartyRolesSubmit($event)"
/>
<span innerHTML="{{item.docName}}"></span>
</label>
</div>
Here i have only used two checkbox as:
The function editPartyRolesSubmit($event) called is:
public documents: Array<Document> = []
public onlyTruedocuments: Array<Document> = [];
editPartyRolesSubmit(event) {
this.documents.filter(x => x.checked).map(x => {
console.log("entered in check");
this.onlyTruedocuments.push(x);
console.log(x);
})
}
The Json data is pushed three times as it should be only pushed two times though i have just clicked two times.
But when i print them in the .html page then though the checkbox is two,It is printed three times :
<li *ngFor="let ot of onlyTruedocuments; index as i">
{{ot.docName}}
</li>
It is printing like this:
How can i remove this redundant data?

If I understand correctly, I would do in this way,
I have used (ngModelChange) instead of (change)
I have passed the current item to the ngModelChange function.
HTML:
<div *ngFor="let item of documents">
<label>
<input type="checkbox" value="{{item.docId}}" [(ngModel)]="item.checked" [name]="item.docName"
(ngModelChange)="editPartyRolesSubmit($event,item)"/> // will pass an item to the function
<span innerHTML="{{item.docName}}"></span>
</label>
</div>
<li *ngFor="let ot of onlyTruedocuments; index as i">
{{ot.docName}}
</li>
TS file:
export class YourComponent {
documents = [{
docId: 1,
checked: false,
docName: 'Prashant'
},
{
docId: 2,
checked: false,
docName: 'Venkat'
}
, {
docId: 2,
checked: false,
docName: 'Perry'
}];
public onlyTruedocuments: any = [];
editPartyRolesSubmit(event, obj) {
// Take the index of an Item checked
let index = this.onlyTruedocuments.indexOf(obj);
// Check for event i.e it is checked or unchecked
if (event) {
if (index == -1) {
// If the index is -1 then that means its not a duplicate so push into an array
this.onlyTruedocuments.push(obj);
}
}
else {
// If it is unchecked then we surely know that the item has to be removed from the array so by an index of the particular item we can [splice][1] the item
this.onlyTruedocuments.splice(index, 1)
}
}
}
No need to filter the source array to get the checked items.
A Working StackBlitz Example with Sample data.

Have a unique check before you push to onlyTruedocuments. This way even when user click many times the object would still have unique values as expected.
editPartyRolesSubmit(event) {
this.documents.filter(x => x.checked).map(x => {
console.log("entered in check");
const exists = this.onlyTruedocuments.filter((f)=>f.docId == x.docId);
if(exists.length==0){
this.onlyTruedocuments.push(x);
}
console.log(x);
})
}

One way to collect unique elements only is to define this.onlyTruedocuments not as an array, but as a Set, initialised as:
this.onlyTrueDocuments = new Set();
Then in the event handler do:
editPartyRolesSubmit(event) {
this.documents.forEach(x => x.checked && this.onlyTruedocuments.add(x));
console.log([...this.onlyTrueDocuments]); // Use spread syntax to get converson to array
}

Related

Unable to add defaults items to angular dropdown

i'm trying to display some default selected data to my angular dropdown menu but for some reason if i push() the items from inside the subscribe() method the items does not being selected and if i try adding the items outside of this specific subscribe method it adds the items to the dropdown without any problems.
NOTE: If i log my array, it gives me all the items, including the items that i added from inside the subscribe()
Part of my component:
public students = []
public loadedSelectedStudents = []
public selectedStudents = []
// Get all selected students
this.studentService.getStaffStudents(+loadedUserId).subscribe(async selectedStudents => {
for (var i = 0; i < selectedStudents.length; i++) {
var selectedStudentId: number = selectedStudents[i].studentId
var studentModel: StaffStudentModel = new StaffStudentModel()
studentModel.userId = +loadedUserId
studentModel.studentId = selectedStudentId
var resStudent: StudentModel = await this.studentService.getStudent(selectedStudentId).toPromise()
// THIS IS NOT WORKING
this.loadedSelectedStudents.push({ student_id: studentModel.studentId, student_name: resStudent.name })
this.selectedStudents.push({ student_id: studentModel.studentId, student_name: resStudent.name})
this.changeDetectorRef.detectChanges();
}
})
// THIS IS WORKING
this.selectedStudents.push({student_id: 5, student_name: "Jonh"})
Part of my service:
getStaffStudents(userId: number) : any {
return this.http.get<StudentModel[]>(this.baseUrl + 'Api/GetStaffStudents/' + userId).pipe(map(res => res))
}
My dropdown settings and FormGroup:
this.studentsDropDownSettings = {
idField: 'student_id',
textField: 'student_name',
allowSearchFilter: true,
noDataAvailablePlaceholderText: "No Available Students"
}
this.studentsDropDownForm = this.fb.group({
studentsItems: [this.selectedStudents]
});
And my HTML element:
<div *ngIf="showStudensMenu" class="form-group">
<form [formGroup]="studentsDropDownForm">
<label for="exampleFormControlSelect1">Students *</label>
<ng-multiselect-dropdown
[settings]="studentsDropDownSettings"
[data]="students"
(onSelect)="onStudentSelected($event)"
(onSelectAll)="onAllStudentsSelected()"
(onDeSelect)="onStudentDeSelected($event)"
(onDeSelectAll)="onAllStudentsDeSelected()"
formControlName="studentsItems">
</ng-multiselect-dropdown>
</form>
</div>
But the multiselect is consuming [data]="students" shouldn't you push the new items to students[]? If the selected values are indeed read from this.selectedStudents you could trigger change detection by resetting the items using spread ... e.g this.selectedStudents = [...this.selectedStudents].
This happens because the html part of the component loads but the data
hasn't come from the api yet i.e. subscription is not yet complete.
You need to set a flag, say isLoaded as true on successful subscription and update the form as well. In html, use a ngIf statement so that the dropdown does not load without the data.
<div *ngIf="isLoaded && showStudensMenu" class="form-group">
<form [formGroup]="studentsDropDownForm">
<!-- your code -->
</form>
</div>
Subscription:
this.studentService.getStaffStudents(+loadedUserId).subscribe(async selectedStudents => {
//your code
this.isLoaded=true;
//Update the Form
this.studentsDropDownForm = this.fb.group({
studentsItems: [this.selectedStudents]
});
}
})
Don't forget to initialize isLoaded as false;

while deleting the particular items from iteration of items in click event first item is deleting instead of clciked one

In my angular application I have some iteration items and saving the items based on adding the items.
.component.html
<ng-container *ngFor="let categoryDetail of selectedCategoryDetails">
<div class="__header">
<div>
<b>{{ categoryDetail.category }}</b>
</div>
</div>
<div
class="clinical-note__category__details"
*ngIf="categoryDetail.showDetails">
<ul>
<li class="habit-list"
*ngFor="let habits of categoryDetail.habitDetails" >
<div class="target-details">
<b>{{ clinicalNoteLabels.target }}: </b
><span class="habit-list__value">{{ habits.target }}</span>
</div>
</li>
</ul>
<div class="habit-footer">
<span class="m-l-10"
[popoverOnHover]="false"
type="button"
[popover]="customHabitPopovers"><i class="fa fa-trash-o" ></i> Delete</span>
</div>
<div class="clinical-note__popoverdelete">
<popover-content #customHabitPopovers [closeOnClickOutside]="true">
<h5>Do you want to delete this habit?</h5>
<button
class="btn-primary clinical-note__save" (click)="deletedata(index);customHabitPopovers.hide()">yes </button>
</popover-content></div>
</div>
</ng-container>
.component.ts
public saveHealthyHabits() {
let isCategoryExist = false;
let categoryDetails = {
category: this.clinicalNoteForm.controls.category.value,
habitDetails: this.healthyHabits.value,
showDetails: true,
};
if (this.customHabitList.length) {
categoryDetails.habitDetails = categoryDetails.habitDetails.concat(
this.customHabitList
);
this.customHabitList = [];
}
if (this.selectedCategoryDetails) {
this.selectedCategoryDetails.forEach((selectedCategory) => {
if (selectedCategory.category === categoryDetails.category) {
isCategoryExist = true;
selectedCategory.habitDetails = selectedCategory.habitDetails.concat(
categoryDetails.habitDetails
);
}
});
}
if (!this.selectedCategoryDetails || !isCategoryExist) {
this.selectedCategoryDetails.push(categoryDetails);
}
this.clinicalNoteForm.patchValue({
category: null,
});
this.healthyHabits.clear();
}
public deletedata(index:number){
if (this.selectedCategoryDetails) {
this.selectedCategoryDetails.forEach((selectedCategory) => {
this.selectedCategoryDetails.splice(index, 1);
}}
From the above code I have saved the data based on adding the items as above and my requirement is when we click on the delete(it will show the popup having the button yes implemented in anbove code).
when we click on the yes button from list of items, I have to remove the particular item
When I tried removing ,It is only deleting the first item instead of clicked one
Can anyone help me on the same
The logic for deletion is incorrect. The splice mutates the original array, and you are applying the loop for deletion, which keeps on iterating over the array and deleting the array elements based on index, instead of deleting single matched index element.
Example -
const categories = [
1,
2,
3,
4
];
function removal(i) {
categories.forEach((category, index) => {
categories.splice(i, 1);
});
console.log('----Categories-->', categories);
}
removal(0);
Categories Array
First Iteration [index = 0]
[1,2,3,4]
Loop Starts Iterating from 1
Second Iteration [index = 1]
[2,3,4]
Loop Starts Iterating from 3
Third Iteration [index = 2]
[3,4]
Stop
Instead you can use filter function.
public deletedata(index:number){
this.selectedCategoryDetails = this.selectedCategoryDetails.filter((_, i) => i! == index);
}
Note - I would recommend to delete the categories based on some identifier like id instead of index because the array elements position can get changed.
Instead of passing index to deleteData method, you can pass the category object.
public deletedata(category){
this.selectedCategoryDetails = this.selectedCategoryDetails.filter((c) => c.id! == category.id);
}

Angular dynamic checkbox Filtering implementation

thanks in advance
my Requirement is to make a custom filter with name wise search(done) and checkboxes which filters a Table's Rows(array of objects) by matching the checkbox value with the Row['tags'] (array of strings) and returns row if the tags array consist of value in a checkbox ,
The problem is that the filters(checkbox) is obtained from DB and Dynamically populated thus I cannot use ngmodel
Any implementation ideas are highly appreciated, I've seen a lot of questions with static filters and some filters using pipes but how to handle the dynamic case
so far my implementation,
Template:
<div id="searchByTag" *ngFor="let tag of tagList">
<input
type="checkbox"
(change)="filterByTags(tag, $event)"
/>{{ tag }}
</div>
Ts:
rows=[{},{}] //from db
temp = rows // copied when getting row from db
filterByTags(FilterTag, event) {
if (event.target.checked) {
const filteredRow = this.rows.filter((obj) => {
return tag.includes(FilterTag.toLowerCase());
});
this.rows = filteredRow;
} else {
return (this.rows = this.temp);
}
}
a Row object:
{
"xx":'yyy',
....,
"tags" : [
"org",
"pcb",
]
}
other problem is that the filtering technique currently returns only one row which matches the condition (cleared), but the main thing is the dynamic implementation of tags
you can have ngModel:
if this is your checkboxes = ["org", "pcb"];
then all you need is a record to bind checkboxes values to it:
checkboxes: {[id: string]: {value: any}} = {};
for(let tag of this.tags) {
this.checkboxes[tag] = {value: false}
}
now in your template:
<input type="checkbox" *ngFor="let item of tags"
[(ngModel)]="checkboxes[item].value">
you can see this in this stackblitz:
stackblitz

Remove items from Array skips certain items

I have the following page (simple):
As you can see, at the top I have an input, then <ul> and finally a button to save changes. My <ul> is bound to a array of items. Once user clicks Uloz zmeny (Save Changes) I am triggering ng-click="vm.SaveChanges()" which looks like following:
vm.SaveChanges = function () {
angular.forEach(vm.items, function (value, key) {
if (value.toRemove == true) {
//remove item from the list
var iIndex = vm.items.indexOf(value);
vm.items.splice(iIndex, 1);
};
});
};
where vm is defined as following at the beginning of my code:
(function () {
"use strict";
angular.module("app-shopping").controller("itemsController", itemsController);
function itemsController($http) {
var vm = this;
vm.items = [];.....more code after here
Every item under my '' has the following structure:
{
"id": 2,
"orderId": 2,
"text": "Item 2",
"toRemove": true
},
Finally, when user checks an item under the <li> I am triggering vm.toggleCompleted() which simply looks like this (it simply changes a boolean state of current item from true to false or vice versa):
vm.toggleCompleted = function (sItem) {
sItem.toRemove = !sItem.toRemove;
};
Here comes the question: Why when I run this code it does not remove all checked items in the array? For example in this specific case (see image above) it would only remove Item 2 and skip Item 3. I believe that the problem is caused by the fact that when Item 2 is remove from the list, Item 3 takes the index of already existing Item 2 and therefore is skipped. Is this assumption correct? If yes, how do I need to change the code to make this run?
P.S. Edit to my code as recommended:
<li class="list-group-item" ng-repeat="sItem in vm.items">
<div class="checkbox checkbox-success">
<input id="ListItem{{$index}}" type="checkbox" placeholder="test placeholder" ng-model="sItem.toRemove" ng-click="sItem.toRemove=!sItem.toRemove" />
<label for="ListItem{{$index}}">{{sItem.text}}</label>
</div>
</li>
I have changed the code the following way and it is working now:
vm.SaveChanges = function () {
for (var i = vm.items.length - 1; i > -1; i--)
{
if (vm.items[i].toRemove == true)
{
vm.items.splice(i, 1);
}
}
};
Instead of using toggleCompleted use below at the place of check-box input
<input type="checkbox" ng-model="item.toRemove" ng-click="item.toRemove=!item.toRemove" />
And Use your new saveChanges method .. this should work fine..
Yes, to bypass this issue, just revert your array traversal, because this way you can guarantee that no position of unchecked elements changes during deletion.
So, you code at the end should be like that:
Template:
<li class="list-group-item" ng-repeat="sItem in vm.items">
<div class="checkbox checkbox-success">
<input id="ListItem{{$index}}" type="checkbox" placeholder="test placeholder" ng-click="sItem.toRemove = !sItem.toRemove" />
<label for="ListItem{{$index}}">{{sItem.text}}</label>
</div>
</li>
<button class="btn btn-success" ng-click="SaveChanges()"> Save</button>
Controller:
$scope.SaveChanges = function () {
for (var i = $scope.vm.items.length - 1; i > -1; i--){
if ($scope.vm.items[i].toRemove) {
$scope.vm.items.splice(i, 1);
};
}
}

How to filter through a table using ng-repeat checkboxes with Angularjs

Once upon a time this was working but somehow it's broken. I want to be able to produce checkboxes using ng-repeat to get as many checkboxes as required based on stored data and use these to filter through a table produced.
Additionally I don't want identical values for the checkboxes to be repeated.
I have made a plnkr with the code.
<div class="row">
<label data-ng-repeat="x in projects">
<input
type="checkbox"
data-ng-true-value="{{x.b}}"
data-ng-false-value=''
ng-model="quer[queryBy]" />
{{x.b}}
</label>
</div>
http://plnkr.co/edit/RBjSNweUskAtLUH3Ss6r?p=preview
So in summary.
Checkboxes to filter Ref.
Checkboxes to be unique.
Checkboxes to be made based off ng-repeat using Ref.
Okay, here's how to do it.
First, let's add a couple of lines of CSS in your to make sure all the checkboxes are visible:
<style>
.row { margin-left: 0px }
input[type=checkbox] { margin-left: 30px; }
</style>
Next, add the following lines to your controller:
app.filter('unique', function() {
return function (arr, field) {
var o = {}, i, l = arr.length, r = [];
for(i=0; i<l;i+=1) {
o[arr[i][field]] = arr[i];
}
for(i in o) {
r.push(o[i]);
}
return r;
};
})
app.controller("maincontroller",function($scope){
$scope.query = {};
$scope.quer = {};
$scope.queryBy = '$';
$scope.isCollapsed = true;
$scope.selectedRefs = [];
$scope.myFilter = function (item) {
var idx = $scope.selectedRefs.indexOf(item.b);
return idx != -1;
};
$scope.toggleSelection = function toggleSelection(id) {
var idx = $scope.selectedRefs.indexOf(id);
if (idx > -1) {
$scope.selectedRefs.splice(idx, 1);
}
else {
$scope.selectedRefs.push(id);
}
};
Phew.
For some reason, your Plunkr's version of AngularJS didn't recognise the unique attribute, so I added one to your controller.
Finally, change your html to this:
<div class="row">
<label data-ng-repeat="x in projects | unique:'b' | orderBy:'b'" >
<input
id="x.b"
type="checkbox"
ng-click="toggleSelection(x.b)"
ng-init="selectedRefs.push(x.b)"
ng-checked="selectedRefs.indexOf(x.b) > -1" />
{{x.b}}
</label>
</div>
... and your ng-repeat to this...
<tr ng-click="isCollapsed = !isCollapsed" ng-repeat-start="x in projects | filter:myFilter | orderBy:orderProp">
If you're interested in knowing how this works, add these lines:
<div style="margin:10px 10px 30px 10px">
<pre>{{ selectedRefs }} </pre>
</div>
I love this trick: you can see the exact contents of our "selectedRefs" array, and see it change as we tick/untick our checkboxes. This really helps when developing/testing our bindings!
As you can see, these changes use the new unique function to get your list of distinct values from your project array, and when the page first loads, we push all of the values into our new "selectedRefs" array.
["123","321","456","654","789","987"]
Then, as you tick/untick the checkboxes, we add/remove that item from this list.
Finally, we use that filter in the ng-repeat.
ng-repeat-start="x in projects | filter:myFilter | orderBy:orderProp"
Job done !
Update
If you wanted to start off with all checkboxes unticked, then it's a simple change. Just remove this line...
ng-init="selectedRefs.push(x.b)"
..and change the myFilter function to show all items initially..
$scope.myFilter = function (item) {
if ($scope.selectedRefs.length == 0)
return true;
var idx = $scope.selectedRefs.indexOf(item.b);
return idx != -1;
};
And to add a "Clear all" button, simply add a button to your form which calls a function in your AngularJS controller like this..
$scope.clearAll = function () {
$scope.selectedRefs = [];
};
(I haven't tested these suggestions though.)
ng-false-value directive needs a value set. Try ng-false-value='false' or ng-false-value='null' (in fact you can skip this one entirely if it has to just be a falsy value and not something concrete, like a string or certain number).
As you've pointed out in the comments, after selecting and then clearing the checkboxes, all rows are filtered out. It happens because unchecking the checkbox will set its value to false, and this does not agree with your entities' values (as you probably know, just stating it for others).
Therefore you do need to set this value to empty string in the end. That'd be the way:
$scope.$watch('quer.$', function () {
if ($scope.quer.$ === false) {
$scope.quer.$ = '';
}
});

Categories

Resources