AngularJS, better way to subtotalize items from ng-repeat - angularjs

I'm doing a table and totalizing all the items, but now I'm trying to get the value from some line only, I'm from Jquery and I'm thinking like Jquery apps, there a good way to do this in AngularJS?
I put a IMG to explain the situation.
Some advice?

I get a good solution, just create and change a new atributte and to call a method that will calculate all value selected.
Enjoy!

You can use angular.Foreach to loop over the selected items and calculate the subtotal.
$scope.subtotal = function() {
var total = 0;
angular.forEach($scope.items, function(item) {
total += item.cost;
})
return total;
}

Assuming that initially your table will have all the checkbox controls unchecked then you can use ng-change directive and call below function:
$scope.changeTotal = function (item) {
if (item.selected) {
$scope.total += item.price;
} else {
$scope.total -= item.price;
}
}
Here is an example: plunker

Related

ui grid returning rows in the same order they are selected?

Hi am using UIgrid in an angularjs project and when I call the method gridApi.selection.getSelectedRows() to get the selected rows it returns an array with all the rows but in a random order. Ideally I want to get the rows in the same order they were selected (as if gridApi.selection.getSelectedRows() is backed by a queue) . Any idea how to achieve this please ?
This link to plunker shows the issue http://plnkr.co/edit/gD4hiEO2vFGXiTlyQCix?p=preview
You can implement the queue by yourself. Something like
$scope.gridOnRegisterApi = function(gridApi) {
gridApi.selection.on.rowSelectionChanged($scope, function(row) {
var selections =gridApi.selection.getSelectedRows();
// add sorted
selections.forEach(function(s){
if ($scope.mySelections.indexOf(s) === -1) {
$scope.mySelections.push(s);
}
});
// remove the ones that are not selected (use for to modify collection while iterating)
for (var i = $scope.mySelections.length; i >0; i--) {
if (selections.indexOf($scope.mySelections[i]) === -1) {
$scope.mySelections.splice(i, 1);
}
}
console.log($scope.mySelections);
row.entity.firstSelection = false;
if (row.isSelected) row.entity.firstSelection = (gridApi.selection.getSelectedCount() == 1);
});
};
I think there is a bug with that version of angular I was using there , if you upgrade the version in the plnkr to 1.6.1 it will behave as expected

Calulation on Angular View

I am working on a cart using Angular, during process of that I faced a issue about calculation.
In my scenario I have code something like
<label>No. of item</label>
<div>{{totalItems}}</div>
<div ng-repeat="cartElement in currentCartDetails.cartElements">
<span>{{cartElement.productName}}</span>
<span>{{cartElement.productCode}}</span>
<span>{{cartElement.productPrice}}</span>
<span>{{cartElement.quantity}}</span>
</div>
What I want, add all something like
totalItems += cartElement.quantity
I know there are so many option to display the value
eg. using calculation from server side, iteration in calculation to controller
But what I am looking for, when I am iterate object on view page is there any way to calculate there and get benefit of tow way binding.
Have you tried to do it with function?
Define a function inside your controller like calculateTotal then call it for every iteration.
$scope.totalItems = 0;
...
$scope.calculateTotal = function(cart){
$scope.totalItems += cart.quantity;
}
...
then in your template
<div ng-repeat="cartElement in currentCartDetails.cartElements" ng-init="calculateTotal(cartElement)">
<span>{{cartElement.productName}}</span>
<span>{{cartElement.productCode}}</span>
<span>{{cartElement.productPrice}}</span>
<span>{{cartElement.quantity}}</span>
</div>
You can also achieve this by ,
Method 1:
HTML :
<div>{{gettotalItems(currentCartDetails.cartElements)}}</div>
JS:
$scope.gettotalItems = function(cart_data){
var num = 0;
for(var i=0;i<cart_data.length;i++) {
num += cart_data[0].quantity;
}
return num;
}
Method 2:
HTML:
<div ng-init="gettotalItems(currentCartDetails.cartElements)>{{totalItems}}</div>
JS:
$scope.gettotalItems = function(cart_data){
var num = 0;
for(var i=0;i<cart_data.length;i++) {
num += cart_data[0].quantity;
}
$scope.totalItems = num;
}
Advantage is You can get the total cart value directly in one call by passing entire List of Objects. instead of, counting each time during iteration.

Trouble filtering angular-meteor collection with infinite-scroll support

I'm trying to create an interactive photo database where the user can sort through photos and filter by user, check multiple categories he or she is interested in, and sort the filtered data by date, number of favorites, etc. The filter/sorting criteria are selected by the user on the DOM and are stored from the client side to $scope.model. The quantity of data visible to the user would be controlled with infinite scroll.
I've created a repository of this example, and I've deployed it here. I've reproduced some of the relevant code from scroll.controller.js below:
Code
// infinite-scroll logic & collection subscription //
$scope.currentPage = 1;
$scope.perPage = 12;
$scope.orderProperty = '-1';
$scope.query = {};
$scope.images = $scope.$meteorCollection(function() {
query={};
// if filtered by a user...
if ($scope.getReactively('model.shotBy')) {
query.shotBy = $scope.model.shotBy;
};
// if category filter(s) are selected...
if ($scope.getReactively('model.category', true)) {
if ($scope.model.category.length > 0){
var categories = [];
for (var i=0; i < $scope.model.category.length; i++){
categories.push($scope.model.category[i]);
}
query.category = {$in: categories};
}
};
$scope.currentPage = 1; // reset
return Images.find(query, { sort: $scope.getReactively('model.sort')});
});
$meteor.autorun($scope, function() {
if ($scope.getReactively('images')){
$scope.$emit('list:filtered');
}
});
$meteor.autorun($scope, function() {
$scope.$meteorSubscribe('images', {
limit: parseInt($scope.getReactively('perPage'))*parseInt(($scope.getReactively('currentPage'))),
skip: 0,
sort: $scope.getReactively('model.sort')
});
});
$scope.loadMore = function() {
// console.log('loading more');
$scope.currentPage += 1;
};
Problem
I can scroll through the images fine, and the infinite-scroll feature seems to work. However, when I attempt to filter the images from the DOM, the only filtered results are those which were initially visible before scrolling, and scrolling doesn't make the rest of the images that meet the criteria show, despite using $scope.$emit to signal ngInfiniteScroll to load more (documentation).
EDIT: If the initial filtered list does, in fact, reach the bottom, scrolling will append properly. It seems to not append only if the initial filtered list doesn't reach the bottom of the screem.
Question
What can I change to make ngInfiniteScroll behave as I would expect on a filtered collection?
Any help, thoughts, or suggestions would be appreciated, and let me know if there's anything else you'd like to see. Thank you!
Well, it took almost all day to figure out, but I now have a working example at this Github repository and deployed here.
To summarize, I found I need to filter at both the collection and subscription levels to cause perPage to apply properly and for ngInfiniteScroll functioning. Also, I needed to send an event via $scope.$emit to ngInfiniteScroll to tell it to fire again in case the images array was too small and didn't reach the edge of the screen. See the Github repository for more details, if you're interested.
Updated relevant code
// infinite-scroll logic & collection subscription //
$scope.currentPage = 1;
$scope.perPage = 12;
$scope.query = {};
function getQuery(){
query={};
// if filtered by a user...
if ($scope.getReactively('model.shotBy')) {
query.shotBy = $scope.model.shotBy;
};
// if category filter(s) are selected...
if ($scope.getReactively('model.category', true)) {
if ($scope.model.category.length > 0){
var categories = [];
for (var i=0; i < $scope.model.category.length; i++){
categories.push($scope.model.category[i]);
}
query.category = {$in: categories};
}
};
return query;
}
$meteor.autorun($scope, function(){
$scope.images = $scope.$meteorCollection(function() {
$scope.currentPage = 1; // reset the length of returned images
return Images.find(getQuery(), { sort: $scope.getReactively('model.sort')});
});
$scope.$emit('filtered'); // trigger infinite-scroll to load in case the height is too small
});
$meteor.autorun($scope, function() {
$scope.$meteorSubscribe('images', {
limit: parseInt($scope.getReactively('perPage'))*parseInt(($scope.getReactively('currentPage'))),
skip: 0,
sort: $scope.getReactively('model.sort'),
query: getQuery()
});
});
$scope.loadMore = function() {
// console.log('loading more');
$scope.currentPage += 1;
};
I'm not sure if I've used best practices with this answer, so please feel free to chime in with suggestions.

How to use Angular Filters on Frontend/Templates

I have a list of numbers repeated horizontally using ng-repeat. At the end of the last number, there is a div where I wish to display the sum of all numbers. How can I do it using filter on screen. Is this possible at all.
I know the way of binding it by a variable in JS. But I want to achieve it in template itself. something like:
Total{{sum(n in repeatedarray)}}
Thanks in advance
You can do this with multiple ways. Please follow below links for quick reference.
Calculating sum of repeated elements in AngularJS ng-repeat
You can create a custom filter for this as specified below.
angular.module('caco.feed.filter', [])
.filter('sumByKey', function() {
return function(data, key) {
if (typeof(data) === 'undefined' || typeof(key) === 'undefined') {
return 0;
}
var sum = 0;
for (var i = data.length - 1; i >= 0; i--) {
sum += parseInt(data[i][key]);
}
return sum;
};
});
For your reference please follow below working example for same.
CodePen

Adding a computed field to every element of an array in an AngularJS model

I'm pulling an array of users into my AngularJS model from a JSON datasource. This data is being rendered in a table, and I'd like to create a column that is computed from two values of the existing user object, without modifying my underlying data service.
// My model
function UserListCtrl($scope,$http) {
$http.get('users').success(function(data) {
$scope.users = data;
});
};
In my partial template, I know I can do something like this:
<tr ng-repeat="for user in users">
<td>{{user.data / user.count | number:2}}</td>
</td>
But I'd rather add that field into the model, so I can use it like so:
<td>{{user.amplification}}</td>
How do I add the "amplification" field to every user in my model?
As an aside, is it possible to use the orderBy filter on something like this:
<td>{{user.data / user.count | number:2}}</td>
You can eather:
Just after loading user do:
$http.get('users').success(function(data) {
$scope.users = data;
$scope.user.amplification() = function() { return $scope.user.data / $scope.user.count; }
});
And use as {{user.amplification()}}
Anywhere at controller:
$scope.$watch('user', function() {
$scope.userAmplification = $scope.user.data / $scope.user.count;
}, true);
$http.get
Or if user.data/count do not change, do same as 1. but staticly calculate:
$http.get('users').success(function(data) {
$scope.users = data;
$scope.user.amplification = $scope.user.data / $scope.user.count;
});
And OrderBy could be used on any expression (uncluding result of other filter)
If you don't need your amplicification() function to update when the data and count properties on your user update, you can do something like this in your controller:
$scope.users.forEach(function(user) {
user.amplification = function() {
return user.data / user.count;
};
});
Adding a second answer as I feel it's appropriate as it's distinct from my first one.
After a little looking around, I found the method I originally posted falls over if you try to add new rows dynamically, or new elements to the array which depend on the computed value. This is because the $scope.array.forEach() will only run when the controller is created.
The best way to solve this problem is to create a properly defined object which contains the options you want. e.g.
function Task(id, name, prop1, prop2) {
this.id = id;
this.name = name;
this.prop1 = prop1;
this.prop2 = prop2;
this.computedProperty = function () {
return this.prop1 + this.prop2;
};
}
This is far more flexible as each new object created will have the new property.
The only downside is that in your ajax success callback, you'll need to pass each of your users into your 'Users()' constructor.
What worked for me was to add a loop and add the property to each item in that loop. I used a property of the controller but I am sure you can use scope the way you are approaching it in the question.
function(result) {
self.list = result;
angular.forEach(self.list, function(item) {
item.hasDate = function() {
return this.TestDate != null;
}.bind(item); // set this context
});
}
Then in my markup I just used it like this.
<div ng-repeat...>
<div ng-show="item.hasDate()">This item has a date.</div>
</div>

Resources