Summing all items contained in the Controller - angularjs

I'm just beginning with AngularJS. I'd need to upgrade this shopping cart example from AngularJS' book so that the total of all (items.price*item.quantity) is displayed at the bottom of the page. Which is the recommended way to achieve it ?
<HTML ng-app>
<head>
<script src="js/angular.min.js"></script>
<script>
function CartController($scope) {
$scope.items = [{
title: 'Paint pots',
quantity: 8,
price: 3.95
},
{
title: 'Pebbles',
quantity: 5,
price: 6.95
}];
$scope.remove = function(index) {
$scope.items.splice(index, 1);
}
}
</script>
</head>
<body ng-controller='CartController'>
<div ng-repeat='item in items'>
<span>{{item.title}}</span>
<input ng-model='item.quantity'>
<span>{{item.price}}</span>
<span>{{item.price * item.quantity}}</span>
<button ng-click="remove($index)">Remove</button>
</div>
</body>
</html>
Thanks!

Here is a plunker:
Create a function which iterates all items like so:
$scope.sum = function(){
return $scope.items.reduce(function(p,item){
return p + (item.quantity * item.price)
},0)
}
Markup:
<span>Sum : {{ sum() }}</span>
read more about reduce method

I think this would be a good candidate for a 'sum' filter. The bonus of writing a sum filter is that it's generic and you can use it anywhere in your app.
The simpliest implementation would take as input an array of objects and a string parameter that is the property on each object to sum.
angular.module('app')
.filter('sum', function () {
return function (input, propertyToSum) {
var sum = 0;
angular.forEach(input, function (value, key) {
sum = sum + value [propertyToSum];
}
return sum;
}
});
Then use it like this:
<span>Sum: {{ items | sum:'price' }}</span>
Not 100% on the syntax here. Build it in fiddler and let me know if it doesn't comes through.
There's a whole host of assumptions being made here that tests and whatnot should cover. But that's the basic idea.
You could also use a utility library like underscore in conjunction with this filter, which provides plenty of useful operations on collections.

Have a total property on the scope with a watch on the item collection:
$scope.total = 0;
$scope.$watch( 'items', updateTotal, true );
function updateTotal(){
$scope.total = 0;
angular.forEach( $scope.items, function(item){
$scope.total += (item.price * item.quantity);
});
}
And in the view:
<p>Total {{total}}</p>

Related

How to calculated angular variable in braces

I have this code:
<pre>totalItems: {{vm.pagination.totalItems}}</pre>
<pre>items-per-page: {{vm.pagination.itemsPerPage}}</pre>
<pre>Total: Math.ceil({{vm.pagination.totalItems / vm.pagination.itemsPerPage}})</pre>
Output:
I'm using Math.ceil() so the answer should be 11 but I'm not getting the correct answer as below:
How can I calculate angular value in braces? Please help and thanks in advance.
The best way is to create a scoped function:
<pre>Total: {{calculateTotal(vm.pagination.totalItems, vm.pagination.itemsPerPage)}}</pre>
Then add the function to the scope:
$scope.calculateTotal = function(totalItems, itemsPerPage) {
return Math.ceil(totalItems/itemsPerPage);
};
angular.module('myapp', [])
.controller('foo', function($scope) {
var vm = this;
vm.w = {
pagination: {
totalItems: 258,
itemsPerPage: 25
}
}
init();
return vm.w;
function init() {
vm.w.pagination.calculation = Math.ceil(vm.w.pagination.totalItems / vm.w.pagination.itemsPerPage)
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<body ng-app="myapp">
<div ng-controller="foo as vm">
<pre>totalItems: {{vm.pagination.totalItems}}</pre>
<pre>items-per-page: {{vm.pagination.itemsPerPage}}</pre>
<pre>Total: {{vm.pagination.calculation}}</pre>
</body>
If you need to use Math in the template, you will need to expose it. So in your controller you would set
vm.Math = Math;
and you would then use it in the template like so:
<pre>Total: {{ vm.Math.ceil(vm.pagination.totalItems / vm.pagination.itemsPerPage }})</pre>
However, rather than doing the calculation in the template you would be better off exposing a function that calculates the value in your controller:
vm.getTotalPageCount = function() {
return Math.ceil(vm.pagination.totalItems / vm.pagination.itemsPerPage);
};
And use it in your template:
<pre>Total: {{ vm.getTotalPageCount() }})</pre>

(newbie) Get data from array of objects using ng-repeat and for loop

I have to access some objects inside an array:
.success(function(data) {
$scope.activities = data.result[0].attributes
});
And I can access this particular index, as expected, inside my view with
<div ng-repeat="x in activities">
<p>{{x.name}}: {{x.value}}</p>
</div>
Now obviously, this only returns the first object in the array, at index 0. (Right?). So I figure I have to somehow loop the function..
.success(function(data) {
for(var i = 0; i < data.result.length; i++) {
$scope.activities = data.result[i].attributes;
}
});
As far as I can tell, the actual for loop is working.. but I need help in the next step exposing this to my view.
Here is my plunker: https://plnkr.co/edit/2ZukY3Oq8vYvghfCruHx?p=preview
(although the data is not available, I have added what the response looks like in the comments)
Well, since the JSON data you pasted in plnkr is invalid, I don't know what attributes means, if it's just an object or it's an array of objects, anyway I made it as a single object.
EDIT
Since now I know attributes is an Array of objects, you can achieve what you want using special repeats.
Here's a snippet working:
(function() {
"use strict";
angular.module('app', [])
.controller('mainCtrl', function($scope) {
$scope.getLeadActivities = function() {
var url = 'xxxxxxxx.mktorest.com';
var endpoint = '/rest/v1/activities.json';
var activityTypeIds = '46';
var access_token = $scope.access_token;
var leadIds = $scope.leadId;
var nextPageToken = $scope.pagingToken;
// This is your data from API
var data = {
"requestId":"14213#155c8de2578",
"success":true,
"nextPageToken":"EOXAPD2V5UOJ5B3S5GGP7NUCX6UI6BFXCWHPJXF245PN2QTGMF3Q====",
"moreResult":false,
"result":[
{
"id":75843491,
"leadId":5334578,
"activityDate":"2016-07-06T06:45:11Z",
"activityTypeId":46,
"primaryAttributeValue":"Web",
"attributes":[
{
"name":"myDataName",
"value":"myDataValue"
}
]
},
{
"id":75843491,
"leadId":5334578,
"activityDate":"2016-07-06T06:45:11Z",
"activityTypeId":46,
"primaryAttributeValue":"Web",
"attributes":[
{
"name":"myDataName2",
"value":"myDataValue2"
}
]
},
{
"id":75843491,
"leadId":5334578,
"activityDate":"2016-07-06T06:45:11Z",
"activityTypeId":46,
"primaryAttributeValue":"Web",
"attributes":[
{
"name":"myDataName3",
"value":"myDataValue3"
}
]
}
]
};
// You'll put it inside the THEN method of your $http.get
$scope.activities = data.result;
}
});
})();
<!DOCTYPE html>
<html ng-app="app">
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.7/angular.min.js"></script>
</head>
<body ng-controller="mainCtrl">
<button ng-click="getLeadActivities()">Get Lead Activities</button>
<div ng-repeat-start="activity in activities"></div>
<ul ng-repeat-end ng-repeat="attr in activity.attributes">
<li ng-bind="attr.name + ': ' + attr.value"></li>
</ul>
</body>
</html>
As per my understanding of your issue, you can solve it by using two ng-repeat clauses.
Your controller code,
.success(function(data) {
$scope.ativitiesArray = data.result;
});
Your html code,
<div ng-repeat="activities in activitiesArray track by $index">
<div ng-repeat="x in activities.attributes track by $index">
<p>{{x.name}}: {{x.value}}</p>
</div>
</div>
I've added track by $index phrase to ng-repeat as it will increase performance. Also, as per suggestions in the comments, please avoid using .success(). It is recommended to use .then().
Hope this solves the issue.

Using ng-repeat in order to iterate over object properties and ordering by value instead of key with ng 1.3

I am in reference to the angular documentation about ngRepeat and iterating over object properties which states:
<div ng-repeat="(key, value) in myObj"> ... </div>
You need to be aware that the JavaScript specification does not define
the order of keys returned for an object. (To mitigate this in Angular
1.3 the ngRepeat directive used to sort the keys alphabetically.)
Say I have the following object:
var myObj = {FOO: 0, BAR: 1};
and want it to be ordered by value (i.e. 0 & 1) instead of keys. How can this be achieved with angular? (I use angular 1.3 by the way).
I have tried:
ng-repeat="(key, value) in myObj | orderBy:value"
But it does not work...
Can anyone please help?
Just as with filter, orderBy works with arrays only. One simple solution is to use a function that returns an array from an object:
<!DOCTYPE html>
<html>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<body>
<div ng-app="myApp" ng-controller="namesCtrl">
<input type="text" ng-model="searchText"/>
<ul ng-init="nameArray=objArray(names)">
<li ng-repeat="x in nameArray | filter:searchText | orderBy: 'value.name'">
{{x.value.name}}
</li>
</ul>
</div>
<script>
angular.module('myApp', []).controller('namesCtrl', function($scope) {
$scope.searchText='';
$scope.names = {
"1": {
"name": "some"
},
"2": {
"name": "values"
},
"3": {
"name": "are"
},
"4": {
"name": "there"
},
"5": {
"name": "here"
}
};
$scope.objArray=function (obj) {
var result=[];
for (var key in obj) {
result.push({
key: key,
value: obj[key]
});
}
return result;
}
});
</script>
</body>
</html>
I believe the best and elegant solution is to make a filter that changes your object into an array. Therefore you can reuse it thought all your project in your template without coding any js.
Here is what the filter would look like:
(function () {
'use strict';
angular.module('app').filter('toArray', toArray);
toArray.$inject = [];
function toArray() {
return toArrayFilter;
function toArrayFilter(input) {
if (angular.isArray(input)) return input;
var list = [];
angular.forEach(input, iterateProperty, list);
return list;
function iterateProperty(elt, key) {
this.push({ key: key, value: elt });
}
}
}
})();
and here is how you would use it in your html template:
<div ng-repeat="element in myObject | toArray | orderBy:value">
{{element.key}}:
<pre>{{element.value | json}}</pre>
</div>

ng-options displays blank selected option, even though ng-model is defined

I have created a plunkr to emphasize the problem, perhaps it's because the source of the ng-repeat is a function, I am not sure, but so far I've tried everything in order to solve this, and couldn't mange.
plunkr:
http://plnkr.co/edit/qQFsRM?p=preview
HTML
<html>
<head>
<script data-require="angular.js#1.2.0-rc1" data-semver="1.2.0-rc1" src="http://code.angularjs.org/1.2.0rc1/angular.js"></script>
<link rel="stylesheet" href="style.css" />
<script src="script.js"></script>
</head>
<body ng-app='myApp' ng-controller='mainCtrl'>
<ng-include src="'menu.html'">
</ng-include>
</html>
Script
var app = angular.module('myApp', []);
app.controller('mainCtrl', function($scope, $httpBackend){
$scope.model = {};
$scope.model.myJobs = {};
$scope.refreshJobs = function(){
}
});
app.controller('menuCtrl', function($scope){
$scope.model.locations = function(){
var loc = [];
loc[1] = 'Dublin';
loc[2] = 'Stockholm';
loc[3] = 'New Jersy';
$scope.model.selectedLocationDef = loc.indexOf('Dublin');
return loc;
}
$scope.model.selectedLocation = $scope.model.selectedLocationDef;
$scope.$watch('model.selectedLocation', function(location){
$scope.refreshJobs(location);
});
});
If you use an array as model, then the model is a string not a number. So you need to convert the number to string. Just try
$scope.model.selectedLocation = '1';
Last I checked, Angular does not support the ability to bind array keys to ng-model via ng-options. You can, however, mimic this behavior using an object hash:
menu.html:
<div ng-controller="menuCtrl">
<select ng-model="model.selectedLocation" ng-options="x.value as x.label for x in model.locations()">
</select>
</div>
script.js:
$scope.model.locations = function(){
var loc = [{
value: 0,
label: 'Dublin'
}, {
value: 1,
label: 'Stockholm'
}, {
value: 2,
label: 'New Jersey'
}];
$scope.model.selectedLocation = 1; // Set default value
return loc;
}
Bear in mind that this will bind the integers to your model, and not the cities themselves. If you want your model value to be Dublin, Stockholm, or New Jersey, simply do:
menu.html:
<div ng-controller="menuCtrl">
<select ng-model="model.selectedLocation" ng-options="name for name in model.locations()">
</select>
</div>
script.js:
$scope.model.locations = function(){
var loc = ['Dublin', 'Stockholm', 'New Jersey'];
$scope.model.selectedLocation = 'Dublin'; // Set default value
return loc;
}

How to remove elements/nodes from angular.js array

I am trying to remove elements from the array $scope.items so that items are removed in the view ng-repeat="item in items"
Just for demonstrative purposes here is some code:
for(i=0;i<$scope.items.length;i++){
if($scope.items[i].name == 'ted'){
$scope.items.shift();
}
}
I want to remove the 1st element from the view if there is the name ted right? It works fine, but the view reloads all the elements. Because all the array keys have shifted. This is creating unnecessary lag in the mobile app I am creating..
Anyone have an solutions to this problem?
There is no rocket science in deleting items from array. To delete items from any array you need to use splice: $scope.items.splice(index, 1);. Here is an example:
HTML
<!DOCTYPE html>
<html data-ng-app="demo">
<head>
<script data-require="angular.js#1.1.5" data-semver="1.1.5" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.1.5/angular.js"></script>
<link rel="stylesheet" href="style.css" />
<script src="script.js"></script>
</head>
<body>
<div data-ng-controller="DemoController">
<ul>
<li data-ng-repeat="item in items">
{{item}}
<button data-ng-click="removeItem($index)">Remove</button>
</li>
</ul>
<input data-ng-model="newItem"><button data-ng-click="addItem(newItem)">Add</button>
</div>
</body>
</html>
JavaScript
"use strict";
var demo = angular.module("demo", []);
function DemoController($scope){
$scope.items = [
"potatoes",
"tomatoes",
"flour",
"sugar",
"salt"
];
$scope.addItem = function(item){
$scope.items.push(item);
$scope.newItem = null;
}
$scope.removeItem = function(index){
$scope.items.splice(index, 1);
}
}
For anyone returning to this question. The correct "Angular Way" to remove items from an array is with $filter. Just inject $filter into your controller and do the following:
$scope.items = $filter('filter')($scope.items, {name: '!ted'})
You don't need to load any additional libraries or resort to Javascript primitives.
You can use plain javascript - Array.prototype.filter()
$scope.items = $scope.items.filter(function(item) {
return item.name !== 'ted';
});
Because when you do shift() on an array, it changes the length of the array. So the for loop will be messed up. You can loop through from end to front to avoid this problem.
Btw, I assume you try to remove the element at the position i rather than the first element of the array. ($scope.items.shift(); in your code will remove the first element of the array)
for(var i = $scope.items.length - 1; i >= 0; i--){
if($scope.items[i].name == 'ted'){
$scope.items.splice(i,1);
}
}
Here is filter with Underscore library might help you, we remove item with name "ted"
$scope.items = _.filter($scope.items, function(item) {
return !(item.name == 'ted');
});
I liked the solution provided by #madhead
However the problem I had is that it wouldn't work for a sorted list so instead of passing the index to the delete function I passed the item and then got the index via indexof
e.g.:
var index = $scope.items.indexOf(item);
$scope.items.splice(index, 1);
An updated version of madheads example is below:
link to example
HTML
<!DOCTYPE html>
<html data-ng-app="demo">
<head>
<script data-require="angular.js#1.1.5" data-semver="1.1.5" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.1.5/angular.js"></script>
<link rel="stylesheet" href="style.css" />
<script src="script.js"></script>
</head>
<body>
<div data-ng-controller="DemoController">
<ul>
<li data-ng-repeat="item in items|orderBy:'toString()'">
{{item}}
<button data-ng-click="removeItem(item)">Remove</button>
</li>
</ul>
<input data-ng-model="newItem"><button data-ng-click="addItem(newItem)">Add</button>
</div>
</body>
</html>
JavaScript
"use strict";
var demo = angular.module("demo", []);
function DemoController($scope){
$scope.items = [
"potatoes",
"tomatoes",
"flour",
"sugar",
"salt"
];
$scope.addItem = function(item){
$scope.items.push(item);
$scope.newItem = null;
}
$scope.removeItem = function(item){
var index = $scope.items.indexOf(item);
$scope.items.splice(index, 1);
}
}
Just a slight expansion on the 'angular' solution. I wanted to exclude an item based on it's numeric id, so the ! approach doesn't work.
The more general solution which should work for { name: 'ted' } or { id: 42 } is:
mycollection = $filter('filter')(myCollection, { id: theId }, function (obj, test) {
return obj !== test; });
My solution to this (which hasn't caused any performance issues):
Extend the array object with a method remove (i'm sure you will need it more than just one time):
Array.prototype.remove = function(from, to) {
var rest = this.slice((to || from) + 1 || this.length);
this.length = from < 0 ? this.length + from : from;
return this.push.apply(this, rest);
};
I'm using it in all of my projects and credits go to John Resig John Resig's Site
Using forEach and a basic check:
$scope.items.forEach(function(element, index, array){
if(element.name === 'ted'){
$scope.items.remove(index);
}
});
At the end the $digest will be fired in angularjs and my UI is updated immediately without any recognizable lag.
If you have any function associated to list ,when you make the splice function, the association is deleted too. My solution:
$scope.remove = function() {
var oldList = $scope.items;
$scope.items = [];
angular.forEach(oldList, function(x) {
if (! x.done) $scope.items.push( { [ DATA OF EACH ITEM USING oldList(x) ] });
});
};
The list param is named items.
The param x.done indicate if the item will be deleted. Hope help you. Greetings.
Using the indexOf function was not cutting it on my collection of REST resources.
I had to create a function that retrieves the array index of a resource sitting in a collection of resources:
factory.getResourceIndex = function(resources, resource) {
var index = -1;
for (var i = 0; i < resources.length; i++) {
if (resources[i].id == resource.id) {
index = i;
}
}
return index;
}
$scope.unassignedTeams.splice(CommonService.getResourceIndex($scope.unassignedTeams, data), 1);
My solution was quite straight forward
app.controller('TaskController', function($scope) {
$scope.items = tasks;
$scope.addTask = function(task) {
task.created = Date.now();
$scope.items.push(task);
console.log($scope.items);
};
$scope.removeItem = function(item) {
// item is the index value which is obtained using $index in ng-repeat
$scope.items.splice(item, 1);
}
});
My items have unique id's. I am deleting one by filtering the model with angulars $filter service:
var myModel = [{id:12345, ...},{},{},...,{}];
...
// working within the item
function doSthWithItem(item){
...
myModel = $filter('filter')(myModel, function(value, index)
{return value.id !== item.id;}
);
}
As id you could also use the $$hashKey property of your model items: $$hashKey:"object:91"

Resources