AngularJS: Count while iterating in nested ng-repeat - javascript

I have created an angularjs application for printing the Indian people count as well as those who have vote eligible count values,
The application is working fine but i dont know how to get indians and vote eligible counts while iterating
Working Demo
<div ng-app='myApp' ng-controller="Controller">
<div ng-init="indiansCount = 0" ng-repeat="emp in records">
<b>Can Vote :</b><br>
<b>Indians :</b> {{getIndiansCount(emp.country, indiansCount)}}
<div ng-repeat="empl in emp">
{{empl.country}}<br>
{{empl.employee.name}}<br>
{{empl.employee.canVote}}
<hr>
</div>
</div>
</div>
Can anyone please tell me some suggestion for this

Your emp.country is undefined, because emp is a collection of employees. You could do this instead:
HTML:
<b>Indians :</b> {{getIndiansCount(emp, indiansCount)}}
JS:
$scope.getIndiansCount = function(employees, count) {
angular.forEach(employees, function(employee) {
if(employee && employee.country === "Indian") {
count++;
}
});
return count;
};
DEMO
EDIT
In case you don't want to add loops, you can indeed use the ng-repeat to execute an increment function.
First you need to initialize an array for indianCounts (and voteCounts) in your scope:
app.controller('Controller', function ($scope) {
$scope.indiansCount = []; // Like this
$scope.voteCount = [];
...
Then you need these functions:
$scope.initCount = function(i) {
$scope.indiansCount[i] = 0;
$scope.voteCount[i] = 0;
}
$scope.incrementCount = function(empl, i) {
if(empl.country === "Indian") {
$scope.indiansCount[i]++;
}
if(empl.employee && empl.employee.canVote === true) {
$scope.voteCount[i]++;
}
};
Finally, here is the HTML with all the stuff needed:
<div ng-app='myApp' ng-controller="Controller">
<!-- Here you keep a trace of the current $index with i -->
<div ng-init="initCount(i = $index)" ng-repeat="emp in records">
<b>Can Vote :</b> {{voteCount[i]}}<br>
<b>Indians :</b> {{indiansCount[i]}}
<div ng-repeat="empl in emp" ng-init="incrementCount(empl, i)">
{{empl.country}}<br>
{{empl.employee.name}}<br>
{{empl.employee.canVote}}
<hr>
</div>
</div>
</div>
Here is the JSFiddle updated

I have updated you jsFiddle.
Added 3 filters -
1. Indian
2. CanVote
3. IndianCanVote
you can see it working here - http://jsfiddle.net/tmu9kukz/7/
Filters
app.filter("Indian", function() {
return function(records) {
var totalIndianCount = 0;
angular.forEach(records, function(emp, empKey) {
angular.forEach(emp, function(oneEmp, oneEmpKey) {
if (oneEmp.country === "Indian") {
totalIndianCount += 1;
}
});
});
return totalIndianCount;
}
});
app.filter("CanVote", function() {
return function(records) {
var totalCanVote = 0;
angular.forEach(records, function(emp, empKey) {
angular.forEach(emp, function(oneEmp, oneEmpKey) {
if (oneEmp.employee.canVote) {
totalCanVote += 1;
}
});
});
return totalCanVote;
}
});
app.filter("IndianCanVote", function() {
return function(records) {
var totalCanVote = 0;
angular.forEach(records, function(emp, empKey) {
angular.forEach(emp, function(oneEmp, oneEmpKey) {
if (oneEmp.country === "Indian" && oneEmp.employee.canVote) {
totalCanVote += 1;
}
});
});
return totalCanVote;
}
})
HTML
<div> Total Indians : {{records | Indian}} </div>
<div> Total Can Vote : {{records | CanVote}} </div>
<div> Total Can Vote : {{records | IndianCanVote}} </div>

Related

AngularJS ng-repeat is slow

It is not like it is slow on rendering many entries. The problem is that whenever the $scope.data got updated, it adds the new item first at the end of the element, then reduce it as it match the new $scope.data.
For example:
<div class="list" ng-repeat="entry in data">
<h3>{{entry.title}}</h3>
</div>
This script is updating the $scope.data:
$scope.load = function() {
$scope.data = getDataFromDB();
}
Lets say I have 5 entries inside $scope.data. The entries are:
[
{
id: 1,
title: 1
},
{
id: 2,
title: 2
},
......
]
When the $scope.data already has those entries then got reloaded ($scope.data = getDataFromDB(); being called), the DOM element for about 0.1s - 0.2s has 10 elements (duplicate elements), then after 0.1s - 0.2s it is reduced to 5.
So the problem is that there is delay about 0.1s - 0.2s when updating the ng-repeat DOM. This looks really bad when I implement live search. Whenever it updates from the database, the ng-repeat DOM element got added up every time for a brief millisecond.
How can I make the rendering instant?
EDITED
I will paste all my code here:
The controller:
$scope.search = function (table) {
$scope.currentPage = 1;
$scope.endOfPage = false;
$scope.viewModels = [];
$scope.loadViewModels($scope.orderBy, table);
}
$scope.loadViewModels = function (orderBy, table, cb) {
if (!$scope.endOfPage) {
let searchKey = $scope.page.searchString;
let skip = ($scope.currentPage - 1) * $scope.itemsPerPage;
let searchClause = '';
if (searchKey && searchKey.length > 0) {
let searchArr = [];
$($scope.vmKeys).each((i, key) => {
searchArr.push(key + ` LIKE '%` + searchKey + `%'`);
});
searchClause = `WHERE ` + searchArr.join(' OR ');
}
let sc = `SELECT * FROM ` + table + ` ` + searchClause + ` ` + orderBy +
` LIMIT ` + skip + `, ` + $scope.itemsPerPage;
sqlite.query(sc, rows => {
$scope.$apply(function () {
var data = [];
let loadedCount = 0;
if (rows != null) {
$scope.currentPage += 1;
loadedCount = rows.length;
if (rows.length < $scope.itemsPerPage)
$scope.endOfPage = true
for (var i = 0; i < rows.length; i++) {
let item = rows.item(i);
let returnObject = {};
$($scope.vmKeys).each((i, key) => {
returnObject[key] = item[key];
});
data.push(returnObject);
}
$scope.viewModels = $scope.viewModels.concat(data);
}
else
$scope.endOfPage = true;
if (cb)
cb(loadedCount);
})
});
}
}
The view:
<div id="pageContent" class="root-page" ng-controller="noteController" ng-cloak>
<div class="row note-list" ng-if="showList">
<h3>Notes</h3>
<input ng-model="page.searchString" id="search"
ng-keyup="search('notes')" type="text" class="form-control"
placeholder="Search Notes" style="margin-bottom:10px">
<div class="col-12 note-list-item"
ng-repeat="data in viewModels track by data.id"
ng-click="edit(data.id)"
ontouchstart="touchStart()" ontouchend="touchEnd()"
ontouchmove="touchMove()">
<p ng-class="deleteMode ? 'note-list-title w-80' : 'note-list-title'"
ng-bind-html="data.title"></p>
<p ng-class="deleteMode ? 'note-list-date w-80' : 'note-list-date'">{{data.dateCreated | displayDate}}</p>
<div ng-if="deleteMode" class="note-list-delete ease-in" ng-click="delete($event, data.id)">
<span class="btn fa fa-trash"></span>
</div>
</div>
<div ng-if="!deleteMode" ng-click="new()" class="add-btn btn btn-primary ease-in">
<span class="fa fa-plus"></span>
</div>
</div>
<div ng-if="!showList" class="ease-in">
<div>
<div ng-click="back()" class="btn btn-primary"><span class="fa fa-arrow-left"></span></div>
<div ng-disabled="!isDataChanged" ng-click="save()" class="btn btn-primary" style="float:right">
<span class="fa fa-check"></span>
</div>
</div>
<div contenteditable="true" class="note-title"
ng-bind-html="selected.title" id="title">
</div>
<div contenteditable="true" class="note-container" ng-bind-html="selected.note" id="note"></div>
</div>
</div>
<script src="../js/pages/note.js"></script>
Calling it from:
$scope.loadViewModels($scope.orderBy, 'notes');
The sqlite query:
query: function (query, cb) {
db.transaction(function (tx) {
tx.executeSql(query, [], function (tx, res) {
return cb(res.rows, null);
});
}, function (error) {
return cb(null, error.message);
}, function () {
//console.log('query ok');
});
},
It is apache cordova framework, so it uses webview in Android emulator.
My Code Structure
<html ng-app="app" ng-controller="pageController">
<head>....</head>
<body>
....
<div id="pageContent" class="root-page" ng-controller="noteController" ng-cloak>
....
</div>
</body>
</html>
So there is controller inside controller. The parent is pageController and the child is noteController. Is a structure like this slowing the ng-repeat directives?
Btw using track by is not helping. There is still delay when rendering it. Also I can modify the entries as well, so when an entry was updated, it should be updated in the list as well.
NOTE
After thorough investigation there is something weird. Usually ng-repeat item has hash key in it. In my case ng-repeat items do not have it. Is it the cause of the problem?
One approach to improve performance is to use the track by clause in the ng-repeat expression:
<div class="list" ng-repeat="entry in data track by entry.id">
<h3>{{entry.title}}</h3>
</div>
From the Docs:
Best Practice: If you are working with objects that have a unique identifier property, you should track by this identifier instead of the object instance, e.g. item in items track by item.id. Should you reload your data later, ngRepeat will not have to rebuild the DOM elements for items it has already rendered, even if the JavaScript objects in the collection have been substituted for new ones. For large collections, this significantly improves rendering performance.
For more information, see
AngularJS ngRepeat API Reference -- Tracking and Duplicates
In your html, try this:
<div class="list" ng-repeat="entry in data">
<h3 ng-bind="entry.title"></h3>
</div>
After thorough research, I found my problem. Every time I reset / reload my $scope.viewModels I always assign it to null / empty array first. This what causes the render delay.
Example:
$scope.search = function (table) {
$scope.currentPage = 1;
$scope.endOfPage = false;
$scope.viewModels = []; <------ THIS
$scope.loadViewModels($scope.orderBy, table);
}
So instead of assigning it to null / empty array, I just replace it with the new loaded data, and the flickering is gone.

How to set default selected text in ng-select or ng-options?

I am fairly new to AngularJS and I have been reading some answers here but nothing worked out. I have a json file from a controller that I display in a select. I want to set the selected value based on the text value.This is what I have so far.
HTML:
<div ng-app="userModule" ng-controller="userCtrl">
<div class="row">
<div class="col-md-6">
<label>User Name:</label> <br />
<select ng-model="users.selectedUser" class="form-control" ng-options="item.UserName as item.UserName for item in users.availableOptions"></select>
</div>
Controller:
<script>
var _$http;
var _$scope;
var oldUser = #Html.Raw(Json.Serialize(ViewData["UserName"]));
var oldRole = #Html.Raw(Json.Serialize(ViewData["RoleName"]));
angular.module('userModule', [])
.controller('userCtrl', xConstructor);
function xConstructor($scope, $http) {
_$http = $http;
_$scope = $scope;
$http.get("/RoleManagement/GetUserData").then(xReceive);
$http.get("/RoleManagement/GetRoleData").then(roleReceive);
_$scope.submit = function () {
//alert("Here:" + _$scope.selectedUser);
$http.get("/RoleManagement/PutUserRoleData?UserId=" + _$scope.selectedUser.UserId + "&RoleId=" + _$scope.selectedRole.RoleId).then(writeSuccess);
}
}
function xReceive(userObject) {
_$scope.users = {
availableOptions: userObject.data,
**selectedUser: { UserId: oldId, UserName: oldUser } //What to put here?**
};
alert(JSON.stringify(JSON.stringify(_$scope.users.selectedUser));
}
</script>
Or any other suggestions on how to do this?
The problem is you are not mapping the model to any element in the array you have.
Assuming you have the id of the user you want to select this is what you do:
function xReceive(userObject) {
_$scope.users = {
availableOptions: userObject.data,
selectedUser: null
};
let selectedUser;
for (let i = 0; i < userObject.data.length; i++) {
if (userObject.data[i].id === oldId) {
selectedUser = userObject.data[i];
break;
}
}
if (selectedUser) {
_$scope.users.selectedUser = selectedUser;
}
alert(JSON.stringify(JSON.stringify(_$scope.users.selectedUser));
}
Also note, you can do this to just select the first one:
_$scope.users.selectedUser = _$scope.users.availableOptions[0];

How to add values of an array dynamically

I have an array with list of values like the following
[
{
"name":"x",
"type":"deposit",
"deposit_amount":100
}
{
"name":"x",
"type":"withdraw",
"withdraw_amount":10
}
{
"name":"y",
"type":"deposit",
"deposit_amount":20
}
{
"name":"y",
"type":"withdraw",
"withdraw_amount":20
}
]
I need to add "deposit_amount" of objects having type as "deposit" and "withdraw_amount" of objects having type as "withdraw".
I have tried using ng-init using ng-repeat
<th ng-show="$last" ng-init="obj.total.deposit_amount = obj.total.deposit_amount + data.deposit_amount">Amount Collected : {{obj.total.deposit_amount}}</th>
<th ng-show="$last" ng-init="obj.total.withdraw_amount = obj.total.withdraw_amount + data.withdraw_amount">Amount Withdrawn :{{obj.total.withdraw_amount}}</th>
When I use this I got the expected one,but each time I click on search the total values get updating.
Any help would be Appreciated.Thanks
Handle that with javascript like this or something.
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope) {
$scope.data = [{"name":"x","type":"deposit","deposit_amount":100},
{"name":"x", "type":"withdraw", "withdraw_amount":10},
{"name":"y", "type":"deposit", "deposit_amount":20},
{"name":"y", "type":"withdraw", "withdraw_amount":20}
];
$scope.totalDeposit = 0;
$scope.totalWithdraw = 0;
angular.forEach($scope.data, function(obj) {
if(obj.type == 'deposit') {
$scope.totalDeposit += obj.deposit_amount;
}
else if(obj.type == 'withdraw') {
$scope.totalWithdraw += obj.withdraw_amount;
}
})
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.4/angular.min.js"></script>
<div ng-app="myApp" ng-controller="myCtrl">
<div>Amount Collected : {{totalDeposit}}</div>
<div>Amount Withdrawn : {{totalWithdraw}}</div>
</div>
var x={}; x.test='xyz'; console.log(x);
--> obj.total.deposit_amount = 'x'

Updating element on firebase binded array on vueJs

I have this Orders array and on the inner div order, I check which status it has.
<div class="list-group" v-for="(order, key) in orders">
<div class="order" v-if="checkOrderStatus(order.status)">
<div class="dishes">
<ul id="dishes" v-for="dish in order.dishes" >
<li v-if="checkDishOnOrder(dish)">{{checkDishOnOrder(dish).quantity}} x {{checkDishOnOrder(dish).dishName}}</li>
</ul>
</div>
<div class="notInclude">
<ul id="notInclude" v-for="dish in order.dishes" >
<li v-if="checkForNotInclude(dish)">{{checkForNotInclude(dish)}}</li>
</ul>
</div>
<div class="table">
<center><span id="table">{{order.tableID}}</span></center>
</div>
<div class="hour">
<center><span id="hour">{{order.hour}}</span></center>
</div>
<div class="status">
<center><button type="button" id="status" :class="{'doingOrder' : order.status == 'Pedido pronto', 'orderDone' : order.status == 'Pronto para entrega'}" #click="changeStatus(order)">{{order.status}}</button></center>
</div>
</div>
</div>
On the beforeCreate: I binded this array with a firebase ref:
this.$bindAsArray('orders', database.ref('orders/' + user.uid).orderByChild('hourOrder'))
The problem is, every time I change a order status the last element of the array changes together and it should not happen.
Here is my checkOrderStatus: function:
checkOrderStatus: function(orderStatus) {
if(this.orderType == 'Em andamento') {
if(orderStatus != "Pronto para entrega") {
return true
}
} else if (this.orderType == 'Pedidos feitos') {
if(orderStatus == "Pronto para entrega") {
return true
}
}
},
Here is changeStatus: function:
changeStatus: function(order) {
var that = this;
var database = Firebase.database();
var loggedUser = Firebase.auth().currentUser;
if (order.status == 'Em andamento') {
order.status = 'Pedido pronto';
var orderKey = order['.key'];
delete order['.key'];
var updates = {};
updates['orders/' + loggedUser.uid + '/'+ orderKey] = order;
database.ref().update(updates).then(function() {
console.log('order Updated');
})
}
else if(order.status == 'Pedido pronto') {
order.status = 'Pronto para entrega';
var orderKey = order['.key'];
delete order['.key'];
var updates = {};
updates['orders/' + loggedUser.uid + '/'+ orderKey] = order;
database.ref().update(updates).then(function() {
console.log('order Updated');
})
}
},
I found a way to avoid this behavior using computed properties:
computed: {
fixedOrders() {
var database = Firebase.database();
this.$bindAsArray('orders', database.ref('orders/' + this.userID).orderByChild('hourOrder'))
return this.orders
},
}
I've binded the array again on a computed property, so that way it always have the right values for orders.
I'm just concerned about the performance loss because I'm biding the orders array again every time it changes.

Dynamic ng-switch inside of ng-repeat

I am trying to create a switch based on a dynamic array of objects...
For example:
<div ng-switch on="currentItem">
<div ng-repeat="item in myItems" ng-switch-when="item.name">
<p>{{item.name}}</p>
<button ng-click="nextItem(item)">Next Item</button>
</div>
</div>
And then in my controller...
$scope.myItems = [{
"name": "one"
}, {
"name": "two"
}]
// Default first item
$scope.currentItem = $scope.myItems[0].name;
$scope.nextItem = function(med) {
for (var i = 0; i < $scope.myItems.length; i++) {
if ($scope.currentItem === $scope.myItems[i].name) {
if ($scope.myItems[i + 1] !== undefined) {
$scope.currentItem = $scope.myItems[i + 1].name
}
}
}
}
Basically, the dom should render a div for each of the items, and when a user clicks the Next Item button, currentItem should be updated, and the switch should trigger based on that.
I am not seeing the first result as I should (nothing is being rendered). Any help would be greatly appreciated.
Plunk: http://plnkr.co/edit/PF9nncd1cJUNAjuAWK22?p=preview
I have forked your plunkr: http://plnkr.co/edit/A9BPFAVRSHuWlmbV7HtP?p=preview
Basically you where not using ngSwitch in a good way.
Just use ngIf:
<div ng-repeat="item in myItems">
<div ng-if="currentItem == item.name">
<p>{{item.name}}</p>
<button ng-click="nextItem(item)">Next Item</button>
</div>
</div>
I've forked your plunkr: http://plnkr.co/edit/2doEyvdiFrV74UXqAPZu?p=preview
Similar to Ignacio Villaverde, but I updated the way your getting the nextItem().
$scope.nextItem = function() {
var next = $scope.myItems[$scope.myItems.indexOf($scope.currentItem) + 1];
if(next) {
$scope.currentItem = next;
}
}
And you should probably keep a reference in currentItem to the entire object, not just the name:
<div ng-repeat="item in myItems">
<div ng-if="item == currentItem">
<p>{{item.name}}</p>
<button ng-click="nextItem(item)">Next Item</button>
</div>
Much simpler!

Categories