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
I normally filter an array using input tag like this :
$scope.filtername = function (vname) {
//repopulate the array from a copy every time input changes
$scope.items = $scope.items_filter;
//filter array if vname is a substring from item.name
if (vname && vname.trim() != '') {
$scope.items = $scope.items.filter((item) => {
return (item.name.toLowerCase().indexOf(vname.toLowerCase()) > -1);
})
}
};
<input type="search" ng-model="namev" ng-change="filtername(namev)">
But since the user needs to filter the array while editing hundreds of items with unsaved changes, I'm not sure how to proceed. Should I use ng-show instead ? or is there a better vanilla javascript way?
do you have a better way to filter array using a search input?
One approach is to create a custom filter:
app.filter("myFilter", function() {
return function(inputArr,vname) {
//repopulate the array from a copy every time input changes
var result = inputArr.concat();
//filter array if vname is a substring from item.name
if (vname && vname.trim() != '') {
result = result.filter((item) => {
return (item.name.toLowerCase().indexOf(vname.toLowerCase()) > -1);
});
};
return result;
};
})
Usage:
<input type="text" ng-model="namev" />
<div ng-repeat="item in items | myFilter : namev">
{{item.name}} {{item.contents}}
</div>
For more information, see AngularJS Developer Guide - Creating Custom Filters
can you explain the purpose of inputArr ?
The first argument to the filter function is the array to be filtered. This way it can be used with other scope variables:
<div ng-repeat="item in items | myFilter : namev">
{{item.name}} {{item.contents}}
</div>
<div ng-repeat="o in otherItems | myFilter : namev">
{{o.name}} {{o.contents}}
</div>
When AngularJS pipes data to a filter, it invokes the filter function with the data as the first argument. Subsequent arguments come from expressions separated by colons (:).
For more information, see AngularJS Developer Guide - Using filters in view templates
Looking through ng-repeats source, it doesn't look like theres any instance of it using for-of. Is there any custom directive that does this or some other way of achieving this loop in templates to make use of iterator functions?
Class with iterator
class Cache{
constructor(items){
this.cache = {
"one" : 1,
"two" : 2
};
};
// custom iterator that turns our cache into an array
// for use in "for...of" loops
[Symbol.iterator](){
var index = 0;
// turn cache object into array of its values (underscore method)
var data = _.values(this.cache);
return {
next: function(){
if(index < data.length){
return {
value: data[index++],
done: false
};
}else{
return { done:true };
}
}
};
};
};
var myCache = new Cache();
// looping my cache in simple js would look like
for(let val of myCache){
console.log(val);
}
// 1, 2
proposed angularjs ng-repeat directive
<ul>
<li ng-repeat="item in myCache track by $index"></li>
</ul>
However that does not work as ng-repeat does not implement for...of. My question is: is there a way to get the ng-repeat directive to work nicely with iterators with minimal interface changes, or better yet, a custom directive identical to ng-repeat that is made for for...of loops?
You could just use Array.from to convert your iterable source to an array, which ngRepeat will be able to iterate:
<ul>
<li ng-repeat="item in Array.from(myCache) track by $index"></li>
</ul>
Ideally this would happen in your javascript directive/controller:
scope.myCache = Array.from(new Cache());
View:
<ul>
<li ng-repeat="item in myCache track by $index"></li>
</ul>
I have a simple list in ui-select, if I choose to delete an item, and load in the ui-select the first element available in the list, the model associated don't get updated. Not sure what I am missing !.
Definition of the ui-select:
<ui-select on-select="loadSelected($item)" ng-model="selectedDude">
<ui-select-match placeholder="{{selectedDude.name}}">
<span> {{selectedDude.name}} </span>
</ui-select-match>
<ui-select-choices repeat="d in data | filter: $select.search">
<span ng-bind-html="d.name | highlight: $select.search"></span>
</ui-select-choices>
</ui-select>
This function is the one I am using for delete:
$scope.deleteSelected= function(){
$scope.data.splice($scope.data.indexOf($scope.selectedDude),1);
$scope.selectedDude = $scope.data[0];
};
Check the example in plunker
Thanks for any help.
I've modified the plunkr for you to get it working. https://plnkr.co/edit/rCKCng6ecXiZ8cNGTBlz?p=preview
First, I added a small utility method in Array to remove an item from a list of objects:
Array.prototype.remove = function(key, value) {
var index = -1;
angular.forEach(this, function(item, i) {
if (item[key] === value) {
index = i;
}
});
if (index > -1) {
this.splice(index, 1);
return true;
}
return false;
};
There were two problems, the first one was related to how you were removing the selectedDude from an array of objects.
$scope.data.splice($scope.data.indexOf($scope.selectedDude), 1);
Since the dude object reference instance stored in the array might be different from what the scope variable selectedDude has. So splice might not work properly all the time as you change anything in it.
So we precisly removing it by searching it through the index (using a utility method).
The second problem was of nested child scope. Read here for more information. We fixed this problem by creating an object dataStore and referencing selectedDude from that object like dataStore.selectedDude to prevent child inheritence problem in Javascript.
I'm trying to create a simple pagination filter for angular, to be used like so:
<ul>
<li ng-repeat="page in items | paginate: 10">
<ul>
<li ng-repeat="item in page">{{ item }}</li>
</ul>
</li>
</ul>
I've written simple function:
angular.module('app.filters', [])
.filter('paginate', function() {
return function(input, perPage) {
var pages = [],
perPage = perPage || 10;
for (i=0; i < input.length; i += perPage)
pages.push(input.slice(i, i + perPage));
return pages;
};
});
And it caused angular to crash with quite cryptic (for me at least) error messages. I figured out that the problem is in nested lists in filter results. To reproduce the problem, it's enough to do like this:
angular.module('app.filters', [])
.filter('paginate', function() {
return function(input, perPage) {
return [[1,2,3],[4,5,6]];
};
});
Can you please tell me:
why nested lists are a problem to angular filters?
where can i read about it in documentation?
how can i eventually write a filter in correct way?
You can see all code in this plunker: http://plnkr.co/edit/gUIJcJg0p5LqKGH10B8t?p=preview
After running the code, open the console, you'll see error messages.
Thank you
I've reported this to angular team, and they say it is expected behaviour:
This is expected behavior. The model can't stabilize because you are
changing the identity of the paginated array each time.
It's better not to use a filter for this, and instead more the
functionality to a $scope.$watch in your controller.
So something like this cannot be done via filter, you'll have to perform pagination in controller, instead of using a filter.
I can't help you with your first question, but I can provide a solution to your problem.
I had a similar issue where I wanted to open and close a every two elements and I fixed it with something like this:
myApp.filter('paginate', ['$cacheFactory', function($cacheFactory) {
var arrayCache = $cacheFactory('paginate');
return function(array, size) {
if (!array) {
return false;
}
var newArray = [];
for (var i = 0; i < array.length; i++) {
newArray.push(array.slice(i, i + size));
}
var arrayString = JSON.stringify(array),
cachedParts = arrayCache.get(arrayString + size);
if (JSON.stringify(cachedParts) === JSON.stringify(newArray)) {
return cachedParts;
}
arrayCache.put(arrayString + size, newArray);
return newArray;
};
}]);
The way you would use this filter, is exactly how you have it in your template, passing the number of elements you want on each page.
About your second question: the best resources I have for Angular are all the videos on egghead.io and A Better Way to Learn AngularJS.
I've been also told that the book Mastering Web Application Development with AngularJS is a great resource too.
After implementing the pagination to my ng-repeat listing (Update pagination in AngularJS after filtering)
<li ng-repeat="data in filtered = (list | filter:search) ... >
I now have a problem with my custom filter
<li ng-repeat="data in filtered = (list | filter:search) | customFilter:search ... >
I need this filter to search by multiple languages (select two or more languages).
If I you replace data in filtered = (list | filter:search) with data in list, you will see it's working. But I need filtered for my pagination.
jsFiddle: http://jsfiddle.net/StinsonMaster/SuEX6/4/ (based on the fiddle from the previous thread)
I think I misunderstood your original question.
I rewrote your custom filter. It's not exactly objected oriented, but with a little touching up it could be much more extensible. Basically you just needed something that was more inclusive than the base angular filter.
app.filter("customFilter", function() {
return function(input, search) {
if(search && search != undefined) {
var _toRet = new Array();
for(var i in search) {
for(var k in input) {
if(search.indexOf(input[k].language) != -1 && _toRet.indexOf(input[k]) == -1) {
_toRet.push(input[k]);
}
}
}
return _toRet;
} else {
return input;
}
};
});
Also please note the changes to your ngRepeat syntax.
ng-repeat="data in filtered = list | filter:search.name | customFilter:search.language | startFrom:(currentPage-1)*entryLimit | limitTo:entryLimit"
http://jsfiddle.net/doublekid/5Bs3h/
I would suggest, instead of using an expression in your ng-repeat directive, set the ng-repeat equal to a method on the list's constroller that returns the subset or values you're looking for. For example:
// In your controller
$scope.filteredList = function() {
return $filter('filter')($scope.list,{'language':search.language});
}
// And in your ng-repeat
ng-repeat="(key,val) in filteredList()"
Hope this helps!