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.
Related
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
for a while I am trying to find how to make a load more(elements from an array) button,using Angular.
I have 9 elements in array, I use ng-repeat to loop them, and limitTo:3 to output first 3.
Questions:
1: is possible to make a load more button using only angular?(load more button is at bottom in example)
2: if not, how to make this work using jQuery?
http://plnkr.co/edit/1gHB9zr0lbEBwlCYJ3jQ
Thanks!
You don't need to think of jQuery, as you could solve this problem easily by using AngularJS itself.
You could maintain a variable inside your controller, name it as limit, then increment the limit variable inside loadMore() function.
Markup
<div ng-repeat="elem in travel.cruise | limitTo:travel.limit" class="cruises">
....COntent here...
</div>
Controller
app.controller('TravelController', function($scope) {
var vm = this;
vm.cruise = cruises;
vm.limit = 3;
$scope.loadMore = function() {
var increamented = vm.limit + 3;
vm.limit = incremented > vm.cruise.length ? vm.cruise.length : increamented;
};
});
Demo Plunkr
You can combine #Pankaj Parkar response with infiniteScroll so you dont need even the button.
No need of cracking any deep coding. Its simple to add just one line of code
<div ng-repeat="elem in travel.cruise | limitTo:travel.limit" class="cruises">
..NG Repeat..
</div>
Controller
$scope.loadMore = function() {
vm.limit = vm.limit + 3;
};
Assigning variable to increase by the 3 or more number will make your code work easily.
Simple and easy trick
i am about to bang my head to walls. i thought i had an understanding of how angular works (filters too). but i just cant find the problem about my filter. it causes infdig. and i even dont change source array in filter.
(function () {
angular.module('project.filters').filter('splitListFilter', function () {
return function (data, chunk) {
if(!data || data.length === 0){
return data;
}
var resultArray = [];
for (var i = 0, j = data.length; i < j; i += chunk) {
resultArray.push(data.slice(i, i + chunk));
}
return resultArray;
};
});
})();
i have lists where i need to split data to x columns. it is complicated to solve with limitTo.
(limitTo: $index*x | limitTo: $last ? -z : -x)
it causes a dirty template file. so i decided to create a filter which splits an array to groups.
[1,2,3,4,5,6,7,8] -> [[1,2,3],[4,5,6],[7,8]]
so i can easily use it in my template.
Can u help me about what causes infdig in this filter?
Edit: the error message itself looks strange with some numbers in that don't appear anywhere in the code, which can be seen at http://plnkr.co/edit/pV1gkp0o5KeimwPlEMlF
10 $digest() iterations reached. Aborting!
Watchers fired in the last 5 iterations: [[{"msg":"fn: regularInterceptedExpression","newVal":23,"oldVal":20}],[{"msg":"fn: regularInterceptedExpression","newVal":26,"oldVal":23}],[{"msg":"fn: regularInterceptedExpression","newVal":29,"oldVal":26}],[{"msg":"fn: regularInterceptedExpression","newVal":32,"oldVal":29}],[{"msg":"fn: regularInterceptedExpression","newVal":35,"oldVal":32}]]
HTML Template
<div class="row" ng-repeat="chunk in docProfile.SysMedicalInterests | splitListFilter: 3">
<div class="col-md-4" ng-repeat="medInterest in chunk">
<label style="font-weight:normal;">
<input type="checkbox" value="{{medInterest.ID}}" ng-click="docProfile.saveInterest(medInterest.ID)" ng-checked="docProfile.isMedChecked(medInterest.ID)"> {{medInterest.Name}}
</label>
</div>
</div>
Controller Code
var me = this;
me['SysMedicalInterests'] = null;
var loadMedicalInterests = function(){
var postData = { 'Data': me['data']['subData'] };
return docService.loadMedicalInterests(postData).then(function(resp) {
me['SysMedicalInterests'] = resp['data'];
}, function(){});
};
loadMedicalInterests();
so array starts with a null reference and loads data from server. which changes array causes a second filter run. but it doesnt stop after that
Edit: here is plunkr http://plnkr.co/edit/OmHQ62VgiCXeVzKa5qjz?p=preview
Edit: related answer on so https://stackoverflow.com/a/21653981/1666060 but this still doesn't explain angular built in filters.
here is angularjs limitTo filter source code
https://github.com/angular/angular.js/blob/master/src/ng/filter/limitTo.js#L3
About what exactly causes it, I suspect is something to do with the fact that every time you run the filter a new array reference is created and returned. However, Angular's built-in filter filter does the same thing, so I'm not sure what is going wrong. It could be something to do with the fact that it's an array of arrays that is being returned.
The best I have come up with is a workaround/hack, to cache the array reference manually as an added property, which I've called $$splitListFilter on the array, and only change it if it fails a test on angular.equals with the correct results calculated in the filter:
app.filter('splitListFilter', function () {
return function (data, chunk) {
if(!data || data.length === 0){
return data;
}
var results = [];
for (var i = 0, j = data.length; i < j; i += chunk) {
results.push(data.slice(i, i + chunk));
}
if (!data.$$splitListFilter || !angular.equals(data.$$splitListFilter, results)) {
data.$$splitListFilter = results;
}
return data.$$splitListFilter;
};
});
You can see this working at http://plnkr.co/edit/vvVJcyDxsp8uoFOinX3V
The answer uses Angular 1.3.15
The JS fiddle works fine: http://jsfiddle.net/3tzapfhh/1/
Maybe you use the filter wrongly.
<body ng-app='app'>
<div ng-controller='ctrl'>
{{arr | splitListFilter:3}}
</div>
</body>
I am passing in 2 arrays to my view. I would like my nested loop to only display where it's parent_id value matches the parent.id. Eg.
arr1 = {"0":{"id":326,"parent_id":0,"title":"Mellow Mushroom voucher","full_name":"Patrick","message":"The voucher says $10 Voucher; some wording on the printout says, \"This voucher is valid for $20 Pizza\" but my purchase price or amount paid also says $20. Shouldn't that be $10","type":"Deals"}};
arr2 = {"0":{"id":327,"parent_id":326,"title":"Re: Mellow Mushroom voucher","full_name":"Patrick Williams","message":"Some message here","type":null};
...
<div data-ng-repeat = "parent in arr1">
<span>{{parent.title}}<span>
<div data-ng-repeat="child in arr2 | only-show-where-child.parent_id == parent.id">
<li>{{child.body}}</li>
</div>
</div>
Is this possible/best practice in angular of should I be filtering the object in node before passing it into angular? Thank you!
There are a couple of ways you could do it... You could create a function to return just the children:
$scope.getChildren = function(parent) {
var children = [];
for (var i = 0; i < arr2.length; i++) {
if (arr2[i].parent_id == parent.id) {
children.push(arr2[i]);
}
}
return children;
};
html:
<div ng-repeat="child in getChildren(parent)">
You could define a filter to do the same thing:
myApp.filter('children', function() {
return function(input, parent) {
var children = [];
for (var i = 0; i < input.length; i++) {
if (input[i].parent_id == parent.id) {
children.push(input[i]);
}
}
return children;
};
});
html:
<div ng-repeat="child in arr2|children:parent">
Both of those methods will execute every digest cycle though. If you have a large list of elements you would definitely want to improve performance. I think the best way would be to pre-process those results when you get them, adding a children array to each object in arr1 with only its children (here using array.filter instead of for loop and array.forEach):
arr1.forEach(function(parent) {
parent.children = arr2.filter(function(value) {
return value.parent_id === parent.id;
};
});
Then in the html you are already working with the parent so you can repeat over its children property:
<div ng-repeat="child in parent.children">
Instead of using filters, data-ng-if can achieve the same result.
<div data-ng-repeat="parent in arr1">
<span>{{parent.title}}<span>
<div data-ng-repeat="child in arr2" data-ng-if="child.parent_id == parent.id">
<li>{{child.body}}</li>
</div>
</div>
The solution depends on how often arrays are changed and how big arrays are.
The fist solution is to use filter. But in this case it would be called at least twice (to make sure that result is "stabilized" - selected same elements).
Other solution is to $watch by yourself original array and prepare "view" version of it injecting children there. Personally I would prefer the second as more explicit.
However if you can reuse "find-the0child" filter in other parts of your application you can go with first one - AngularJS will re-run filter only after original array modified.
If needed I can provide here an example of implementation of one of these options - add the comment to answer.
I'm trying to achieve customized numbering while listing all items in an array.
All items in array are rendered using ng-repeat & ui.sortable.
Numbering must be done in such a way that, for an array item "statement", count should not be increased & displayed.
(Else I may be used $index instead of an external count.)
For any other array item, count should be increased & displayed.
The solution that got me the the closest result was the one where I passed $index into a filter function written in the controller.
like in HTML:
<li ng-repeat="question in questions">
<div class="no"> {{ filterIndex(question.question, $index) }} </div>
<div>{{question.question}}</div>
</li>
in controller:
var filterValue = 0;
$scope.filterIndex = function (value, count) {
if (count === 0) {
filterValue = 0;
}
if (value !== 'statementText') {
filterValue = filterValue + 1;
return filterValue;
}
else {
return '"';
}
};
Even it was working without any errors, the count returned from function is not get updated like we get with $index when we update the order using ui-sortable.
see it here: js-fiddle using filter function
means, once it rendered (4) in <[ (4) fourth question ]> will remain same even if we moved it to top or bottom by dragging.
I tried different ways and almost everything ended up on 'Maximum iteration limit exceeded.'.
Real scenario is a little bit complex as it contains nested ng-repeats and similar counting with digits and numbers alternatively in child loops.
Here is link to start fresh:
js-fiddle
You can inject this to your controller, it will listen for array changes, and update the indexes:
var update = function(){
var currentCount = 0;
var questions = $scope.questions;
for(var j = 0; j < questions.length; j++){
if(questions[j].question != null){
if(questions[j].question.indexOf("statement") < 0){
questions[j].calculatedIndex = ++currentCount;
}
}
}
};
$scope.$watchCollection('questions', function(){
update();
});
update();
probably needs a bit fine-tuning as I didn't concentrate on your question completely. In the ng-repeat, you now have access to calculatedIndex. It will be "NULL" for statement items, you can use ng-show for that.
just try to add extra option:
ng-init='question.index = $index+1'
http://jsfiddle.net/ny279bry/