angular ng-repeat in reverse - javascript

How can i get a reversed array in angular?
i'm trying to use orderBy filter, but it needs a predicate(e.g. 'name') to sort:
<tr ng-repeat="friend in friends | orderBy:'name':true">
<td>{{friend.name}}</td>
<td>{{friend.phone}}</td>
<td>{{friend.age}}</td>
<tr>
Is there a way to reverse original array, without sorting.
like that:
<tr ng-repeat="friend in friends | orderBy:'':true">
<td>{{friend.name}}</td>
<td>{{friend.phone}}</td>
<td>{{friend.age}}</td>
<tr>

I would suggest using a custom filter such as this:
app.filter('reverse', function() {
return function(items) {
return items.slice().reverse();
};
});
Which can then be used like:
<div ng-repeat="friend in friends | reverse">{{friend.name}}</div>
See it working here: Plunker Demonstration
This filter can be customized to fit your needs as seen fit. I have provided other examples in the demonstration. Some options include checking that the variable is an array before performing the reverse, or making it more lenient to allow the reversal of more things such as strings.

This is what i used:
<alert ng-repeat="alert in alerts.slice().reverse()" type="alert.type" close="alerts.splice(index, 1)">{{$index + 1}}: {{alert.msg}}</alert>
Update:
My answer was OK for old version of Angular.
Now, you should be using
ng-repeat="friend in friends | orderBy:'-'"
or
ng-repeat="friend in friends | orderBy:'+':true"
from https://stackoverflow.com/a/26635708/1782470

Sorry for bringing this up after a year, but there is an new, easier solution, which works for Angular v1.3.0-rc.5 and later.
It is mentioned in the docs:
"If no property is provided, (e.g. '+') then the array element itself is used to compare where sorting". So, the solution will be:
ng-repeat="friend in friends | orderBy:'-'" or
ng-repeat="friend in friends | orderBy:'+':true"
This solution seems to be better because it does not modify an array and does not require additional computational resources (at least in our code). I've read all existing answers and still prefer this one to them.

Simple solution:- (no need to make any methods)
ng-repeat = "friend in friends | orderBy: reverse:true"

You can reverse by the $index parameter
<tr ng-repeat="friend in friends | orderBy:'$index':true">

You can just call a method on your scope to reverse it for you, like this:
<!doctype html>
<html ng-app="myApp">
<head>
<script src="http://code.jquery.com/jquery-1.9.1.min.js"></script>
<script src="http://code.angularjs.org/1.0.5/angular.min.js"></script>
<script>
angular.module('myApp', []).controller('Ctrl', function($scope) {
$scope.items = [1, 2, 3, 4];
$scope.reverse = function(array) {
var copy = [].concat(array);
return copy.reverse();
}
});
</script>
</head>
<body ng-controller="Ctrl">
<ul>
<li ng-repeat="item in items">{{item}}</li>
</ul>
<ul>
<li ng-repeat="item in reverse(items)">{{item}}</li>
</ul>
</body>
</html>
Note that the $scope.reverse creates a copy of the array since Array.prototype.reverse modifies the original array.

if you are using 1.3.x, you can use the following
{{ orderBy_expression | orderBy : expression : reverse}}
Example List books by published date in descending order
<div ng-repeat="book in books|orderBy:'publishedDate':true">
source:https://docs.angularjs.org/api/ng/filter/orderBy

If you are using angularjs version 1.4.4 and above,an easy way to sort is using the "$index".
<ul>
<li ng-repeat="friend in friends|orderBy:$index:true">{{friend.name}}</li>
</ul>
view demo

When using MVC in .NET with Angular you can always use OrderByDecending() when doing your db query like this:
var reversedList = dbContext.GetAll().OrderByDecending(x => x.Id).ToList();
Then on the Angular side, it will already be reversed in some browsers (IE). When supporting Chrome and FF, you would then need to add orderBy:
<tr ng-repeat="item in items | orderBy:'-Id'">
In this example, you'd be sorting in descending order on the .Id property. If you're using paging, this gets more complicated because only the first page would be sorted. You'd need to handle this via a .js filter file for your controller, or in some other way.

You can also use .reverse(). It's a native array function
<div ng-repeat="friend in friends.reverse()">{{friend.name}}</div>

That's because you are using JSON Object. When you face such problems then change your JSON Object to JSON Array Object.
For Example,
{"India":"IN","America":"US","United Kingdon":"UK"} json object
[{"country":"India","countryId":"IN"},{"country":"America","countryId":"US"},{"country":"United Kingdon","countryId":"UK"}]

The orderBy filter performs a stable sorting as of Angular 1.4.5. (See the GitHub pull request https://github.com/angular/angular.js/pull/12408.)
So it is sufficient to use a constant predicate and reverse set to true:
<div ng-repeat="friend in friends | orderBy:0:true">{{friend.name}}</div>

I found something like this, but instead of array i use objects.
Here is my solution for objects:
Add custom filter:
app.filter('orderObjectBy', function() {
return function(items, field, reverse){
var strRef = function (object, reference) {
function arr_deref(o, ref, i) {
return !ref ? o : (o[ref.slice(0, i ? -1 : ref.length)]);
}
function dot_deref(o, ref) {
return !ref ? o : ref.split('[').reduce(arr_deref, o);
}
return reference.split('.').reduce(dot_deref, object);
};
var filtered = [];
angular.forEach(items, function(item) {
filtered.push(item);
});
filtered.sort(function (a, b) {
return (strRef(a, field) > strRef(a, field) ? 1 : -1);
});
if(reverse) filtered.reverse();
return filtered;
};
});
Which can then be used like
<div ng-repeat="(key, value) in items | orderObjectBy:'field.any.deep':true">
If you need old browser support, you will need to define the reduce function (this is only available in ECMA-262 mozilla.org)
// Production steps of ECMA-262, Edition 5, 15.4.4.21
// Reference: http://es5.github.io/#x15.4.4.21
if (!Array.prototype.reduce) {
Array.prototype.reduce = function(callback /*, initialValue*/) {
'use strict';
if (this == null) {
throw new TypeError('Array.prototype.reduce called on null or undefined');
}
if (typeof callback !== 'function') {
throw new TypeError(callback + ' is not a function');
}
var t = Object(this), len = t.length >>> 0, k = 0, value;
if (arguments.length == 2) {
value = arguments[1];
} else {
while (k < len && !(k in t)) {
k++;
}
if (k >= len) {
throw new TypeError('Reduce of empty array with no initial value');
}
value = t[k++];
}
for (; k < len; k++) {
if (k in t) {
value = callback(value, t[k], k, t);
}
}
return value;
};
}

I had gotten frustrated with this problem myself and so I modified the filter that was created by #Trevor Senior as I was running into an issue with my console saying that it could not use the reverse method. I also, wanted to keep the integrity of the object because this is what Angular is originally using in a ng-repeat directive. In this case I used the input of stupid (key) because the console will get upset saying there are duplicates and in my case I needed to track by $index.
Filter:
angular.module('main').filter('reverse', function() {
return function(stupid, items) {
var itemss = items.files;
itemss = itemss.reverse();
return items.files = itemss;
};
});
HTML:
<div ng-repeat="items in items track by $index | reverse: items">

Im adding one answer that no one mentioned. I would try to make the server do it if you have one. Clientside filtering can be dangerous if the server returns a lot of records. Because you might be forced to add paging. If you have paging from the server then the client filter on order, would be in the current page. Which would confuse the end user. So if you have a server, then send the orderby with the call and let the server return it.

Useful tip:
You can reverse you're array with vanilla Js: yourarray .reverse()
Caution: reverse is destructive, so it will change youre array, not only the variable.

I would sugest using array native reverse method is always better choice over creating filter or using $index.
<div ng-repeat="friend in friends.reverse()">{{friend.name}}</div>
Plnkr_demo.

Related

How to pass ng-repeat item property to filter?

I created a filter that I want to pass an item property, and not the item itself. Is that possible?
The following does not work (item.param1 fails):
ng-reapeat="item in items | filter : fnFilter(item.param1)"
$scope.fnFilter = function(value) {
return value == "test";
}
Your question is quite unclear sdince you don't tell really what is your goal.
So if i just restrict to what i see there and following the link Angular filter exactly on object key already provided by #CeylanMumumKocabas you would have
ng-repeat="item in items | filter:{'param1': 'test'}"
Now let's consider you want something more complex : the only way i see would be to pass the name of the attribute to the filter :
ng-reapeat="item in items | myFilter:'param1'"
myApp.filter('myFilter', function () {
return function(inputs,attributeName) {
var output = [];
angular.forEach(inputs, function (input) {
if (input[attributeName] == 'test')
output.push(input);
});
return output;
};
});
Note that if you want to go more than one level, you'll have to use $eval or make add some code for this to work.

Substring a JSON object value in an ng-repeat

I was wondering what would be the best way to accomplish getting a substring of a value from a JSON object that is being spit out in an ng-repeat. Currently I have:
<tr data-ng-repeat=" item in records | orderBy : '-score' | limitTo : 10 " ng-click="moreInfo(item)">
<td>{{$index+1}}</td>
<td>{{item.name}}</td>
<td>{{item.score}}</td>
</tr>
And what is displayed on the page is user-SmithJoe. I was wondering if it is possible in the html to do something like {{item.name.substring(5,item.name.length())}}. I am sure that doesnt work, but hopefully you understand what I am trying to accomplish from that. I ultimately just want SmithJoe to be the output.
This is the js:
data2.forEach(function(r) {
if (r && r.user && r.user.toLowerCase().indexOf($scope.searchText.toLowerCase()) !== -1) {
$scope.records.push(r);
}
});
It the for each part is just going through all of the returned json objects and adding them to records[] if they contain what I am looking for. So since I did it this way I didn't really see there any way to take a substring # that point?
Thanks.
Add a property to the object when you iterate:
data2.forEach(function(r) {
if (r && r.user && r.user.toLowerCase().indexOf($scope.searchText.toLowerCase()) !== -1) {
r.prettyName = r.name.replace("user-", "");
$scope.records.push(r);
}
});
And output that name:
<td>{{item.prettyName}}</td>
{{item.name.slice(5)}}
should works :)

AngularJS Custom Filter orderBy sortPredicate not working

The problem is that I have an array of recipe objects. Each recipe object has some comments on it. I want to sort the array in angularJS controller using the $filter service provided by angular.
$scope.recipes = $filter('orderBy')($scope.data, function(recipe) {
return recipe.comments.length;
});
But its not giving the required results. However, I'm able to achieve the desired results using the JS array sort functionality like this
$scope.data.sort(function(a, b) {
if (a.comments.length < b.comments.length) return 1;
if (b.comments.length < a.comments.length) return -1;
return 0;
});
The Plunkr for the same scenario is : http://plnkr.co/edit/L9Bt67xHRCJLBoWG8EZp?p=preview
Thanks in advance. Please Help!
It can be done a lot simpler using orderBy
http://plnkr.co/edit/B0fMi7FotgmG2tkCjySt?p=preview
<ul>
<li ng-repeat="r in recipes | orderBy:'-comments.length'">
{{r.title}} - {{r.comments.length}}
</li>
</ul>
Added this as another answer, since you want to manage it in your controller and you want the reverse, add true as the final arg in $filter
Documentation
$filter('orderBy')(array, expression, reverse)
Example
$scope.recipes = $filter('orderBy')($scope.data, function(recipe) {
return recipe.comments.length;
}, true);
I'm pretty sure you can also set the reverse to a var in scope if you wish.
$scope.recipes = $filter('orderBy')($scope.data, "comments.length", true)
The filter expects an expression, not a function.

How do you sort an AngularFire collection?

$scope.items = [];
var ref = new Firebase("https://****.firebaseio.com/");
var query = ref.limit(5);
angularFire(query, $scope, "items");
<div ng-repeat="item in items" >
<span>{{item.title}}</span>
</div>
How would you sort these by "title"? I've seen a couple similar questions but the solutions don't seem to work, at least for this situation.
If $scope.items were an array of objects (as implied by $scope.items = []), you would be able to use the angular orderBy filter:
<!-- This example will NOT work with Firebase -->
<div ng-repeat="item in items | orderBy:'title'">
<span>{{item.title}}</span>
</div>
However, setting items to an array as you did is misleading, because you are actually storing (and retrieving) your "items" as a list of key value pairs, not an array. Sorting objects in javascript is more complicated in general, but one way to achieve what you want is a custom filter:
app.filter('orderObjectBy', function(){
return function(input, attribute) {
if (!angular.isObject(input)) return input;
var array = [];
for(var objectKey in input) {
array.push(input[objectKey]);
}
function compare(a,b) {
if (a[attribute] < b[attribute])
return -1;
if (a[attribute] > b[attribute])
return 1;
return 0;
}
array.sort(compare);
return array;
}
});
Now you can use your filter to order by any of your item properties:
<div ng-repeat="item in items | orderObjectBy:'title'">
<span>{{item.title}}</span>
</div>
Here is a fiddle to demonstrate. As #bennlich noted below, your custom filter could stop at the array conversion and then you could use the normal orderBy filter, as suggested here.
And just to be thorough I should note that you could also sort "server-side" with Firebase priorities.

angularjs ng-repeat on two levels but just one output

I have a big object that looks something like this :
scope.marketplaces = {
first_example : { ... },
second_example : { ... },
...
};
What I'm trying to do is loop through the big object like this :
<section>
<ul>
<li ng-repeat="(key, value) in marketplaces"></li>
</ul>
</section>
And inside the loop, loop again each object, but instead of appending to DOM something like :
<li> ... first level loop ...
<li> ... second level loop ... </li>
</li>
I would like to have just one <li></li> despite the level I'm looping through. The reason why I want it like that is because I need the key from the first loop to be referenced down the level loops and still have just one li.
I've read that ng-repeat="friend in friends | filter:searchText" could do what I want but I'm not sure if in the filter expression I can dynamically specify the key or something else that is needed instead of searchText ( I guess that is an already know property of the object ).
So my question is, can I achieve what I just explained with the filter or is there another way of doing it ?
I hope I've understood question: you would like to have one ngRepeat on a nested object. So kind of linearize object.
First approach would be to create filter:
app.filter('linear', function() {
return function(value) {
var result = {};
for(var key in value) {
for(var cKey in value[key]) {
result[key+'_'+cKey] = value[key][cKey];
}
}
return result;
};
});
and in thml:
<li ng-repeat="(key, value) in marketplaces | linear">
{{key}}: {{value}}
</li>
But unfortunally AngularJS have problems when in filter you create new elements. You can have error message in console kind of:
10 $digest iterations reached
Without hacks with $$hash you can't achieve that functionality for now (Please correct me if I am wrong).
So I think for now the solution would be to watch 'marketplaces' in controller and create new object using same code as in controller that use in ngRepeat:
$scope.$watch('marketplaces', function(value) {
var result = {};
for(var key in value) {
for(var cKey in value[key]) {
result[key+'_'+cKey] = value[key][cKey];
}
}
$scope.linearMarketplaces = result;
}, true);
And in HTML:
<li ng-repeat="(key, value) in linearMarketplaces">
{{key}}: {{value}}
</li>
Plunker with both solutions: http://plnkr.co/edit/pYWFhqSqKAa9gpzek77P?p=preview

Categories

Resources