Angularjs orderBy in ng-repeat not working as expected - angularjs

I have the following div in my template:
<div ng-repeat="item in billing_history | orderBy:'-timestamp'">{{ item.date }}</div>
console.log(JSON.stringify($scope.billing_history)) gives me the following:
{
"May, 2015":{
"date":"May, 2015",
"timestamp":1432921230
},
"March, 2015":{
"date":"March, 2015",
"timestamp":1427846400
},
"February, 2015":{
"date":"February, 2015",
"timestamp":1425168000
}
}
No matter what, this is what is displayed:
February, 2015
March, 2015
May, 2015
I've tried orderBy:'-timestamp' and orderBy:'+timestamp'
I'm really not sure why this isn't working. Does anyone see anything that could be going wrong?

You cannot use order-by filter with an object literal (or it wont just work as expected). What you have is an object literal basically there is no specific guaranteed ordering for the keys (and so for its values). You would need to convert it to an array.
Example:
angular.module('app', []).run(function($rootScope) {
$rootScope.billing_history = [{
"date": "May, 2015",
"timestamp": 1432921230
}, {
"date": "March, 2015",
"timestamp": 1427846400
}, {
"date": "February, 2015",
"timestamp": 1425168000
}]
})
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app">
<p>Reverse order:
<div ng-repeat="item in billing_history | orderBy:'-timestamp'">{{ item.date }}</div>
<p>Correct order:
<div ng-repeat="item in billing_history | orderBy:'timestamp'">{{ item.date }}</div>
</div>
Filter is one option, but be careful, filters are very performance intensive (they run as many times every digest cycle to stabilize and so what is in the filter matters much) and for operations like this on a large object it is very tricky. So better to set up view model appropriately or convert the format in the controller itself.

orderBy filter works on an array and you are using it on a object. Convert the object into array and try it.
Demo
http://plnkr.co/edit/4XtrJQaJ1itrVbb9bwE7?p=preview

You can create your own custom filter as Justin Klemm does here
app.filter('orderObjectBy', function() {
return function(items, field, reverse) {
var filtered = [];
angular.forEach(items, function(item) {
filtered.push(item);
});
filtered.sort(function (a, b) {
return (a[field] > b[field] ? 1 : -1);
});
if(reverse) filtered.reverse();
return filtered;
};
});
And then your markup turns into:
<div ng-repeat="item in billing_history | orderObjectBy:'timestamp'">
{{ item }}
</div>
Or
<div ng-repeat="item in billing_history | orderObjectBy:'-timestamp'">
{{ item }}
</div>
See this jsBin

Related

Is there a way to escape dot notation in AngularJS groupBy expression?

I am using this AngularJS filter: https://github.com/a8m/angular-filter#groupby
I have a piece of data (JSON) involving key names containing dots, which seems to be mistaken with nested properties.
Is there a way to prevent the filter from parsing the dot notation or an alternate way to specify the name of the field used for grouping ?
Note: I can't change the way data are build from the server.
My code looks like this :
<div ng-repeat="(key, values) in items | groupBy: 'category_id.name'" >
<div class="item-row">
{{key}}
</div>
<div class="badge-row" ng-repeat="item in values">
{{item.name}}
</div>
</div>
And here is the JSON data I get from the server :
[
{
"id": "1",
"name": "test",
"category_id.name": "Main"
},
{
"id": "2",
"name": "foo",
"category_id.name": "Other category"
},
{
"id": "3",
"name": "bar",
"category_id.name": "Test"
}
]
When running this code, at the moment, I am getting 'undefined' as key value.
Angular Filter uses $parse, so that you can order by fields in nested objects
I have a workaround for this problem. If it helps.
Template:
(key, values) in items | groupBy: rawProperty('category_id.name')
Controller:
$scope.rawProperty = function(key) {
return function(item) {
return item[key];
};
};
JS Bin Demo
Hope it helps.

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>

Angular Filter Dates (Past / Future) with Moment.js

Say I have JSON data with objects like so:
$scope.events = [
{
title: 'Event 1',
date: '2015-04-11 10:00'
},
...
];
And I display it using ng-repeat:
<ul>
<li ng-repeat="event in events">{{event.title}}</li>
</ul>
How would I create a filter to only show future events and hide past ones (compare event.date to the current time)? Normally I would use Moment's .isBefore().
UPDATE:
I'd like the name of the date property to be configurable.
Yes. You should define a custom filter using Moment's isAfter method.
An example can be found on http://onehungrymind.com/build-custom-filter-angularjs-moment-js/
Their definition of the filter, they've even made the cutoff date configurable:
.filter('isAfter', function() {
return function(items, dateAfter) {
// Using ES6 filter method
return items.filter(function(item){
return moment(item.date).isAfter(dateAfter);
})
}
})
Or if you don't want to make that configurable:
.filter('isFuture', function() {
return function(items) {
return items.filter(function(item){
return moment(item.date).isAfter(new Date());
})
}
})
To apply it:
ng-repeat="event in events | isAfter:date"
or
ng-repeat="event in events | isFuture"
UPDATE:
You'd like to make the name of the date property configurable. You can do that. The trick in calling it is that item.date is the same as item['date'].
Second trick is that you can provide a default value for a parameter by using ||. param || 'default' will use the param value if it is provided, and 'default' if it is undefined.
So
.filter('isFuture', function() {
return function(items, dateFieldName) {
return items.filter(function(item){
return moment(item[dateFieldName || 'date']).isAfter(new Date());
})
}
})
Then in the ng-repeat you get for the default:
ng-repeat="event in events | isFuture"
or to specify the field name:
ng-repeat="event in events | isFuture:'deadline'"
even simpler, in your controller:
$scope.now = moment().format(); //normalised format (ISO 8601)
in your view
<ul>
<li ng-repeat="event in events">
<span ng-if="event.date > now">{{event.title}}<span>
</li>
</ul>
Just normalise your event.date with the "2014-09-08T08:02:17-05:00" (ISO 8601) format, you can use moment().format() as well

Angular order dropdown select list

How I can order the list in select dropdown using Angular ?
This is the angular controller:
var app = angular.module("app", []);
app.controller('Ctrl', function($scope, $filter, $http) {
$scope.options= [
{
"id": 823,
"value": "81"
},
{
"id": 824,
"value": "77"
},
{
"id": 825,
"value": "152"
},
];
});
And html:
<h4>Angular-xeditable Editable row (Bootstrap 3)</h4>
<div ng-app="app" ng-controller="Ctrl">
<select ng-model="test" ng-options="v.id as v.value for v in options | orderBy: value"></select>
</div>
Now the order is: 81, 77, 152
And I want: 77,81,152
How I can do it ?
Jsfiddle to test
Thanks
Your values are strings so they won't be naturally ordered unless you convert them to integers.
You may convert them by creating your own filter or by defining a simple sorting function on your $scope:
Fiddle
<select
ng-model="test"
ng-options="v.id as v.value for v in options | orderBy: naturalOrder"
></select>
$scope.naturalOrder = function(item){
return parseInt(item.value, 10);
};
You need to place single quotes around the name of the field in your orderBy. The first argument to orderBy is a sort expression string.
in options | orderBy:'value'
You can extend this by adding a + or - before the field name to indicate initial sort direction. You can also provide a boolean value after the sort expression to enable toggling of the sort direction.
<div ng-init="desc=true">
...
in options | orderBy:'+value':desc

AngularJS - custom filter with ngRepeat directive

I would like to use my custom filter with ngRepeat directive. Here is what I have
HTML:
<div ng-app="menuApp">
<ul ng-controller="MenuCtrl">
<li ng-repeat="item in menuItems | rootCategories">
{{item.Name}}
</li>
</ul>
</div>
JS:
angular.module('menuApp', [])
.filter('rootCategories', function() {
return function(item) {
return item.Parent == 0;
};
});
function MenuCtrl($scope) {
$scope.menuItems = [{ "Id": 1, "Name": "Sweep", "Parent": 0 }];
/*
$scope.rootCategories = function(item) {
return item.Parent == 0;
};
*/
};
I do not want to use the commented out way to filter my items, because the real filter will be complicated than in the provided example. For some reasons input parameter "item" is not defined, therefore I see nothing. Could you tell me what is wrong? Thank you.
Please have a look at this fiddle. http://jsfiddle.net/bKFXy/.
This uses the syntax of passing a predicate object as the filter expression.
item in menuItems | filter:{Parent:0}
There is another method for filtering and the fiddle for that is here http://jsfiddle.net/kd5dv/

Resources