How to define a model for dynamically created checkboxes in angular - javascript

I am creating a table of users where I want to add a checkbox on each row and a delete button. When I click the delete button, I want to delete all users who were selected.
Now I am creating these user entries from an API response which gives me say id, name and email.
So my view looks something like this:
<tr ng-repeat="user in MyCntrl.data.users track by $index">
<td><input type="checkbox"></td>
<td>{{user.name}}</td>
<td>{{user.email}}</td>
</tr>
What I want in my controller is to have an object with id of all the users for whom the checkbox was clicked.
Even if I create an object and assign it as model for checkbox, how do I add a key as id in that object?

You could simply do <input type="checkbox" ng-model="user.isSelected">
And then just filter MyCntrl.data.users for those that have isSelected === true

Because of JavaScript dynamic typing nature, nothing stops you from adding a field named 'isSelected' (or alike) to your models. Then, you can add ng-model="user.isSelected" to your checkbox tag.
Then, on deletion, check which entries have isSelected set to true and delete them.

Here's an example for how you can track all the selected users in an another array:
Example: Plunker
<tr ng-repeat="user in MyCntrl.data.users track by $index">
<td><input type="checkbox" ng-model="tempVar" ng-click="toggleSelection($index)"></td>
<td>{{user.name}}</td>
<td>{{user.email}}</td>
</tr>
<!-- AngularJS Code -->
<script type="text/javascript">
var app = angular.module('myApp', []);
app.controller('MyCtrl', function($scope) {
$scope.selectedUsers = []; // initially the selected users array is empty
$scope.toggleSelection = function(index){
var positionInSelectedArray;
var arr = $scope.MyCntrl.data.users;
var tempUser = arr[index]; // refers to the selected user object in $scope.MyCntrl.data.users array (further, we'll call it "arr")
var userAlreadySelected = $scope.selectedUsers.filter(function( obj ) {
return obj.userId == tempUser.userId;
})[0]; //checks whether the user is already selected or not (if selected, then returns the user object)
if (angular.isUndefined(userAlreadySelected)) {
$scope.selectedUsers.push(tempUser); //insert the object in array containing selected users
}else{
positionInSelectedArray = $scope.selectedUsers.indexOf(userAlreadySelected);
$scope.selectedUsers.splice(positionInSelectedArray, 1); //removes the user object from array containing selected users
}
};
});
</script>

Related

how to set checkbox checked based on value in one array and add value to another array if checked otherwise remove using angular js?

enter image description hereFollowing is just the overview of the code,as given in html code i just want to show the options from options array from set object and have to set checkbox checked to option which is an answer from answer array and have to add new answer to answer if i check more options with checkbox clicked, and have to remove answer if checkbox is unchecked.
<script>
var adminApp = angular.module('app',[]);
adminApp.controller('EditController', function ($scope) {
$scope.viewQuestions=function(){
set={}; //object in which answer array and option array is present //assume;
var answer= ["answer1", "answer2"]; //answer array assume
var options=["option1,option2,option3,option4"]; //option array assume
var answerType="Multiple";
}
$scope.updateAnswer =function(isSet,index,answer,set)
{
for(var i=0;i<set.answer.length;i++)
{
if(isSet===set.answer[i])
{
set.answer[index]=isSet;
}
else
{
set.answer.splice(index, 1);
}
}
}
}
</script>
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.16/angular.js"></script>
</head>
<body ng-app="app" ng-controller="EditController" ng-init="viewQuestions()">
<table>
<tr>
<td ng-show="s.answerType === 'Multiple'">
<p ng-repeat="o in s.options">{{$index + 1}}. <input type="checkbox"
name="answer-{{index}}"
ng-model="isSet" value="{{o}}"
ng-change="updateAnswer(isSet,$index,answer,s)">{{o}}</p>
</td>
</tr>
</table>
</body>
</html>
This is not exactly what you want but it's something. I change the concept to do the same in a cleaner way and more angular style. (in my opinion)
I have an array of objects (name: The option title & active: Checked or not) And after each change I update the set. With filter & map; So, the set is always up to date
(If you receive a array of string as options, you can assume that all of them are Active: false)
Maybe it can works for you in general, or you can get an idea from the code.
http://plnkr.co/edit/GucWDwF66A56IkXHHpwG?p=preview
In angular, you can bind checkbox's checked property to a method that returns ids of your mini_array that is in main_array;
---your.html---
enter code here
enter code h
<div class="mb-2"><b>Permissions list</b></div>
<div formArrayName="permissions" *ngFor="let permission of main_array; let i=index">
<input class="mr-2" [checked]="isChecked(permission.id)" type="checkbox">
{{permission.name}}
</div>
---your.ts---
isChecked(id) {
return this.mini_array.includes(id);
}

Angular 1: check all check boxes in a large ng-repeat

I have an table created using ng-repeat and there hundreds of rows, up to 600 or 700. Each row includes a checkbox and I have a "Check All" box at the top to check all the boxes in one go. However I'm running into browser performance issues, IE11 (the clients preferred choice) in particular becomes unresponsive. After several minutes all the checkboxes appear checked but you still can't scroll or do anything so it is effectively useless.
I have created a controller array and when the checkAll box is clicked it loops through the model (the one used in ng-repeat) and adds a value to the array. I presume it's this looping through the array that is causing the slow-down but I'm not sure. Pagination has been ruled out, they want all the rows on one page.
<table>
<tbody>
<tr>
<th>Table Header</th>
<th><input type="checkbox" id="checkAllCheckBox" ng-model="vm.allChecked" ng-change="vm.tickOrUntickAllCheckBoxes()" />
</tr>
<tr ng-repeat="payment in vm.payments>
<td>{{ payment.somePaymentValue }}</td>
<td>
<input type="checkbox" class="paymentsApprovalCheckbox"
ng-checked="vm.approvedPayments.indexOf(payment.payId) > - 1"
ng-value="payment.payId" ng-model="payment.approved"
ng-click="vm.handleCheckBoxClick(payment.payId)" />
</td>
</tr>
</tbody>
</table>
Here is the angular function that checks/unchecks all
vm.tickOrUntickAllCheckBoxes = function(){
if (vm.allChecked == false) {
vm.approvedPayments = [];
} else {
vm.payments.forEach(function(payment){
vm.approvedPayments.push(payment.payId);
});
}
};
Swapping out the angular vm.tickOrUntickAllCheckBoxes() function for a plain old javascript option makes the checkAll box work almost instantaneously in IE11 however I lose access to the checked payment.payId values. I wonder is there away for angular to get them? Here is the plain javascript checkAll() function:
<script>
function checkAll(x) {
var checkBoxes = document.getElementsByClassName('paymentsApprovalCheckbox');
for (var i = 0; i < checkBoxes.length ; i++) {
checkBoxes[i].checked = (x.checked == true);
}
}
</script>
Then I update the checkAll checkbox like this:
<input type="checkbox" id="checkAllCheckBox" ng-model="vm.allChecked" onclick="checkAll(this)" />
If you check one checkbox individually then the ng-model="payment.approved" in the repeating checkboxes is updated but this does not happen if they are checked with the checkAll function. Is it possible for angular to detect the boxes checked with checkAll()? I guess this is just putting off the same old inevitable slow-down to a slightly later point in the process.
Anyone have any ideas or work-arounds? Thanks!
I would use the ng-model to the best of its abilities. In your controller:
$onInit() {
// If you need this from a REST call to populate, you'll have to
// remember to do that here;
this.model = {
all: true,
items: {}
};
}
In your loop:
<tr>
<th>Table Header</th>
<th>
<input type="checkbox"
id="checkAllCheckBox"
ng-model="vm.model.all"
ng-change="vm.tickOrUntickAllCheckBoxes()" />
</tr>
<tr ng-repeat="payment in vm.payments track by $index">
<td ng-bind="payment.somePaymentValue"></td>
<td>
<input type="checkbox"
class="paymentsApprovalCheckbox"
ng-change="vm.approvedPayments($index)"
ng-model="vm.model.items[$index]" />
</td>
</tr>
Then in your controller:
tickOrUntickAllCheckBoxes() {
const boxes = this.model.items.length;
this.model.all = !this.model.all;
// Several ways to do this, forEach, map, etc.,
this.model.items.forEach((item) => { item.checked = !this.model.all });
}
And for setting it individually:
approvedPayments(idx) {
// Sets all the item's boxes checked, or unchecked;
this.model.items[idx].checked = !this.model.items[idx].checked;
// Possible call to extended model to get payment info;
handleCheckBoxClick(idx);
}
You should be able to put all the payment information into the one approvedPayments() method rather than have two separate methods (move logic out of template and into the controller or a service). I.e., your model could look like:
this.model.items = [
// One 'option' with id, payment etc;
{
id: 73,
paymentId: 73,
somePaymentValue: 210.73,
currencyType: 'GBP',
checked: false
},
{
// Another 'option' etc...
}
]
One issue to note is the incompatibility of ngChecked with ngModel, had to look it up (which is why I haven't used ng-checked in the above).
Thank to everyone for the suggestions. The solution I came up with was to push some of the work back to the server side. Instead of just loading the payments model (in which each payment record contains a lot of info) i am now loading two additional models when the page loads, one of which is a set of key/value pairs where the keys are payId and the values are all false and another one with the same keys and all values are true. Example:
{
"1": false,
"2": false
}
These are used for the checkAll/Uncheck all - just set the vm.approvedIDs variable to the true or false one. Then, the vm.approvedIDs variable is used as the model in the ng-repeat checkbox.
I have to do a bit of extra work on the server side when the user sends the approvedIDs back to the server to get only the key/id of the 'true' entries. Here are the relevant angular controller functions:
$onInit() {
// call http to get 'data' from server
vm.payments = data.payments;
vm.paymentIDsFalse = vm.approvedIDs = data.paymentIDsFalse;
vm.paymentIDsTrue = data.paymentIDsTrue;
};
// tick/untick all boxes
vm.tickOrUntickAllCheckBoxes = function(){
if (vm.allChecked == false) {
vm.approvedPayments = vm.paymentIDsFalse;
} else {
vm.approvedPayments = vm.paymentIDsTrue;
}
};
// tick/untick one box
vm.handleCheckBoxClick = function(payId, currentValue){
vm.approvedPayments[payId] = currentValue;
};
vm.submitApprovedIds = function(){
// post vm.approvedPayments to server
};
HTML:
<table>
<tbody>
<tr>
<th>Table Header</th>
<th><input type="checkbox" id="checkAllCheckBox" ng-model="vm.allChecked" ng-change="vm.tickOrUntickAllCheckBoxes()" />
</tr>
<tr ng-repeat="payment in vm.payments>
<td>{{ payment.somePaymentValue }}</td>
<td>
<input type="checkbox" class="paymentsApprovalCheckbox"
ng-value="payment.payId"
ng-model="vm.approvedPayments[payment.payId]"
ng-click="vm.handleCheckBoxClick(payment.payId, vm.approvedPayments[payment.payId])" />
</td>
</tr>
</tbody>
</table>
It looks to me as if there must be a better way than creating these additional models but it is working pretty smoothly for now and I can move on to the next thing!

How to update only specific rows when using ng-repeat

I have a UI similar to excel, where the user can change the data in which ever row he wants.
Currently i am using ng-repeat to display the existing data, after fetching from mongoDB. This data is an array of Json Objects , so ng-repeat works fine.
Now i want to have a single save button, clicking which, all the edits should be saved. My question is how to maintain or know which rows have been changed ?? i can just send the whole array and update in the db, but when the number of rows is large and data of just one row has changed there will be an unnecessary overhead. so let me know how this can be optimised.
Thanks in advance
Add an ng-change event to fields where they can edit the values.
On ng-change, set a new key 'modified: true' on 'obj in objects'.
Something like this:
<div ng-repeat="row in rows track by $index">
<input type="text" ng-change="markAsChanged($index)" ng-model="row.textValue">
</div>
And your JS:
$scope.markAsChanged = function(index) {
$scope.rows[index].changed = true;
}
Then your update function could iterate all rows and send changed rows to the webservice for updating instead of sending them all.
$scope.rowsChanged = [];
$scope.Changed = function(row) {
$scope.rowsChanged.push(row);
}
<div ng-repeat="row in rows track by $index">
<input type="text" ng-change="Changed(row)" ng-model="row.textValue">
</div>
Hope it helps..
This thing can be achieved by maintaining an array which contain index for all the rows that has changed. On clicking on save button , an iteration will be required on this array (For rows which have changed).
A new $index can be pushed to this array anytime when the user is just clicking on a row or on changing any input in the array.
HTML:
<div ng-repeat="row in rows track by $index">
<input type="text" ng-change="markAsChanged($index)" ng-model="row.textValue">
</div>
Controller:
var listOfChangedRows = [];
$scope.markAsChanged = function(index){
if(listOfChangedRows.indexOf(index)==-1)
{
listOfChangedRows.push(index);
}
}
//Save button Clicked
$scope.saveEditedRows = function(){
var updatedItems = [];
for(var i=0;i<listOfChangedRows.length;i++)
{
updatedItems.push($scope.rows[i]);
}
//Now send the updatedItems array to web service
}

How to use checkbox filter in angularjs? [duplicate]

I have an AngularJS directive that renders a collection of entities in the following template:
<table class="table">
<thead>
<tr>
<th><input type="checkbox" ng-click="selectAll()"></th>
<th>Title</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="e in entities">
<td><input type="checkbox" name="selected" ng-click="updateSelection($event, e.id)"></td>
<td>{{e.title}}</td>
</tr>
</tbody>
</table>
As you can see, it's a <table> where each row can be selected individually with its own checkbox, or all rows can be selected at once with a master checkbox located in the <thead>. Pretty classic UI.
What is the best way to:
Select a single row (i.e. when the checkbox is checked, add the id of the selected entity to an internal array, and add a CSS class to the <tr> containing the entity to reflect its selected state)?
Select all rows at once? (i.e. do the previously described actions for all rows in the <table>)
My current implementation is to add a custom controller to my directive:
controller: function($scope) {
// Array of currently selected IDs.
var selected = $scope.selected = [];
// Update the selection when a checkbox is clicked.
$scope.updateSelection = function($event, id) {
var checkbox = $event.target;
var action = (checkbox.checked ? 'add' : 'remove');
if (action == 'add' & selected.indexOf(id) == -1) selected.push(id);
if (action == 'remove' && selected.indexOf(id) != -1) selected.splice(selected.indexOf(id), 1);
// Highlight selected row. HOW??
// $(checkbox).parents('tr').addClass('selected_row', checkbox.checked);
};
// Check (or uncheck) all checkboxes.
$scope.selectAll = function() {
// Iterate on all checkboxes and call updateSelection() on them??
};
}
More specifically, I wonder:
Does the code above belong in a controller or should it go in a link function?
Given that jQuery is not necessarily present (AngularJS doesn't require it), what's the best way to do DOM traversal? Without jQuery, I'm having a hard time just selecting the parent <tr> of a given checkbox, or selecting all checkboxes in the template.
Passing $event to updateSelection() doesn't seem very elegant. Isn't there a better way to retrieve the state (checked/unchecked) of an element that was just clicked?
Thank you.
This is the way I've been doing this sort of stuff. Angular tends to favor declarative manipulation of the dom rather than a imperative one(at least that's the way I've been playing with it).
The markup
<table class="table">
<thead>
<tr>
<th>
<input type="checkbox"
ng-click="selectAll($event)"
ng-checked="isSelectedAll()">
</th>
<th>Title</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="e in entities" ng-class="getSelectedClass(e)">
<td>
<input type="checkbox" name="selected"
ng-checked="isSelected(e.id)"
ng-click="updateSelection($event, e.id)">
</td>
<td>{{e.title}}</td>
</tr>
</tbody>
</table>
And in the controller
var updateSelected = function(action, id) {
if (action === 'add' && $scope.selected.indexOf(id) === -1) {
$scope.selected.push(id);
}
if (action === 'remove' && $scope.selected.indexOf(id) !== -1) {
$scope.selected.splice($scope.selected.indexOf(id), 1);
}
};
$scope.updateSelection = function($event, id) {
var checkbox = $event.target;
var action = (checkbox.checked ? 'add' : 'remove');
updateSelected(action, id);
};
$scope.selectAll = function($event) {
var checkbox = $event.target;
var action = (checkbox.checked ? 'add' : 'remove');
for ( var i = 0; i < $scope.entities.length; i++) {
var entity = $scope.entities[i];
updateSelected(action, entity.id);
}
};
$scope.getSelectedClass = function(entity) {
return $scope.isSelected(entity.id) ? 'selected' : '';
};
$scope.isSelected = function(id) {
return $scope.selected.indexOf(id) >= 0;
};
//something extra I couldn't resist adding :)
$scope.isSelectedAll = function() {
return $scope.selected.length === $scope.entities.length;
};
EDIT: getSelectedClass() expects the entire entity but it was being called with the id of the entity only, which is now corrected
I prefer to use the ngModel and ngChange directives when dealing with checkboxes. ngModel allows you to bind the checked/unchecked state of the checkbox to a property on the entity:
<input type="checkbox" ng-model="entity.isChecked">
Whenever the user checks or unchecks the checkbox the entity.isChecked value will change too.
If this is all you need then you don't even need the ngClick or ngChange directives. Since you have the "Check All" checkbox, you obviously need to do more than just set the value of the property when someone checks a checkbox.
When using ngModel with a checkbox, it's best to use ngChange rather than ngClick for handling checked and unchecked events. ngChange is made for just this kind of scenario. It makes use of the ngModelController for data-binding (it adds a listener to the ngModelController's $viewChangeListeners array. The listeners in this array get called after the model value has been set, avoiding this problem).
<input type="checkbox" ng-model="entity.isChecked" ng-change="selectEntity()">
... and in the controller ...
var model = {};
$scope.model = model;
// This property is bound to the checkbox in the table header
model.allItemsSelected = false;
// Fired when an entity in the table is checked
$scope.selectEntity = function () {
// If any entity is not checked, then uncheck the "allItemsSelected" checkbox
for (var i = 0; i < model.entities.length; i++) {
if (!model.entities[i].isChecked) {
model.allItemsSelected = false;
return;
}
}
// ... otherwise ensure that the "allItemsSelected" checkbox is checked
model.allItemsSelected = true;
};
Similarly, the "Check All" checkbox in the header:
<th>
<input type="checkbox" ng-model="model.allItemsSelected" ng-change="selectAll()">
</th>
... and ...
// Fired when the checkbox in the table header is checked
$scope.selectAll = function () {
// Loop through all the entities and set their isChecked property
for (var i = 0; i < model.entities.length; i++) {
model.entities[i].isChecked = model.allItemsSelected;
}
};
CSS
What is the best way to... add a CSS class to the <tr> containing the entity to reflect its selected state?
If you use the ngModel approach for the data-binding, all you need to do is add the ngClass directive to the <tr> element to dynamically add or remove the class whenever the entity property changes:
<tr ng-repeat="entity in model.entities" ng-class="{selected: entity.isChecked}">
See the full Plunker here.
Liviu's answer was extremely helpful for me. Hope this is not bad form but i made a fiddle that may help someone else out in the future.
Two important pieces that are needed are:
$scope.entities = [{
"title": "foo",
"id": 1
}, {
"title": "bar",
"id": 2
}, {
"title": "baz",
"id": 3
}];
$scope.selected = [];

ng-if not working as expected

This is how I populate the Table and attach checkbox to controller
<tr ng-repeat="key in queryResults.userPropNames">
<td><input type="checkbox"
data-ng-checked="selectedKeys.indexOf(key) != -1"
data-ng-click="toggleSelect(key)">
</td>
<td>{{key}}</td>
<td ng-repeat="user in queryResults.users">
{{user.properties[key]}}
</td>
</tr>
This is how my HTML for button looks
<div>
<span ng-if="!validKeys" class="button-terminal primary save-user-keys"
data-disabled="false">Save Keys</span>
<span ng-if="validKeys" class="button-terminal primary save-user-keys"
data-ng-click="saveUserKeys()">Save Keys</span>
</div>
and my Controller looks like
$scope.toggleSelect = function (attribute) {
if ($scope.selectedKeys.indexOf(attribute) === -1) {
$scope.selectedKeys.push(attribute);
} else {
$scope.selectedKeys.splice($scope.selectedKeys.indexOf(attribute), 1);
}
};
$scope.saveUserKeys = function() {
$scope.customAttributes.mappingForUser = $scope.selectedKeys;
$scope.saveMappings();
};
$scope.validKeys = !!$scope.selectedKeys;
But my button is always enabled, even if I de-select all the checkboxes
What is wrong with this code?
Thank you
$scope.selectedKeys is an Array, even when no keys are selected. However empty Arrays are truthy (!![] // => true).
One fix would be to check the length of selectedKeys instead:
$scope.validKeys = $scope.selectedKeys && $scope.selectedKeys.length;
Alternatively, if assigning validKeys was just an attempt to get the view to render correctly, on the view you could just update the ngIf to ng-if="selectedKeys.lengh"
If you print validKeys (i.e. {{validKeys}}, do you see it changing between true/false? Also, if I understand this correctly, you should be testing for the length of validKeys - if higher than 0, enable the button, otherwise disable it.

Categories