I implemented a custom filter for my ng-table, where it uses ngTagInput. Link[1] is similar to my code, this filter does works only with the current page.
What is the correct way of getting all the results to get filtered in ng-repeat.
code snippet for filter:
.filter('filterByTags', function () {
return function (items, tags) {
var i = 0;
var filtered = []; // Put here only items that match
(items || []).forEach(function (item) { // Check each item
var matches = tags.some(function (tag) { // If there is some tag
i++;
return (item.name.indexOf(tag.name) > -1) // that is a substring
}); // we have a match
if (matches) { // If it matches
filtered.push(item); // put it into the `filtered` array
}
});
if(i == 0){
return items;
}
else{
return filtered;
}
};
})
[1] Filter ngtagsinput in AngularJS
I also had the problem that the filter was working only on the current page. The original question did not include the HTML, just a link to similar code, so I'm not sure if my solution would be applicable to the original question, but it might help others struggling with paging and filtering. My HTML looked like this, with the paging before the filtering:
<tr ng-repeat="item in items | pages: myCtrl.currentPage : myCtrl.itemsPerPage | filter: searchCriteria">
I changed the HTML to put the filtering before the paging, and then the filter worked on all the pages:
<tr ng-repeat="item in items | filter: searchCriteria | pages: myCtrl.currentPage : myCtrl.itemsPerPage">
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.
I have an array of objects in $scope.currentSChannels.scgsLink This array of objects have something like
$scope.currentSChannels.scgsLink = [{channelId:1, sCgsLinkId:1, groupNo:1, percentage: 50, expireHrs:4},{channelId:1, sCgsLinkId:2, groupNo:2, percentage:50, expireHrs:1}]
and I also have the following select list
<div class="col-md-4">
<select class="form-control" ng-model="newLink.groupNo"
name="groupNo" id="groupNo"
ng-options="t.value as t.text for t in metaData.spGroups"></select>
</div>
I need to filter that list to not show already selected items in the $scope.currentSChannels.scgsLink groupNo column. I looked at http://christian.fei.ninja/Angular-Filter-already-selected-items-from-ng-options/ and also at AngularJS ng-options to exclude specific object and both seem to be close but not enough as I need to filter against an array and a particular column in that array. How should I implement that filtering?
The template is getting a bit tricky. Assuming selectedLink is the variable that points to the selected groupNo
ng-options="t.value as t.text for t in metaData.spGroups | filter: {value: '!' + currentSChannels.scgsLink[selectedLink].groupNo}"
See this fiddle : the second select contains the same collection as the first one, excluded what is already selected.
Edit: Solution above is for excluding elements according to one value. So as to exclude the elements according to a collection of values, a custom filter would suit best:
Filter
app.filter('channelFilter', function () {
return function (metadata, exclusions) {
var filterFunction = function (metadata) {
// return the metadata object if exclusions array does NOT contain his groupNo
return !exclusions.some(function (exclusion) {
return exclusion.groupNo === metadata.value;
});
};
return metadatas.filter(filterFunction);
};
});
Usage
ng-options="metadata in metadatas | channelFilter: exclusions"
Template
ng-options="t.value as t.text for t in metaData.spGroups | channelFilter: currentSChannels.scgsLink"
Fiddle
That said, would be more efficient to group selected links by groupNo to avoid searches in the array, and filter in the controller.
I wanted to make it a bit more generic, so I've done the following
http://jsfiddle.net/96m4sfu8/
app.filter('excludeFrom', function () {
return function (inputArray, excludeArray, excludeColumnName, inputColumnName) {
if (inputColumnName==undefined)
inputColumnName = 'value';
var filterFunction = function (inputItem) {
return !excludeArray.some(function (excludeItem) {
return excludeItem[excludeColumnName] === inputItem[inputColumnName];
});
};
return inputArray.filter(filterFunction);
};
});
I have an issue with a custom filter I am working on, I am struggling with this because I am using Angular 1.3.6 and I can not upgrade now, so I really need your help in this case.
see this Plnkr
if you type 1H the filter returns all the leagues with those characters, which is awesome, but, if you type... let's say: College, the filter returns all the sports starting with College but the leagues of that sports disappears, my filter works great with the leagues, but if you try to find any of the sports, then his leagues disappears and I do not want that, I want that if you search through the sports, the filter must returns every sports with their leagues respectively.
I want to keep the same behavior when you search through leagues, that is awesome now, my issue is just with sports. See my code below
filter.js
.filter('myFilter', function() {
return function(sports, filterBy) {
if (!sports || !filterBy) {
return sports;
}
filterBy = filterBy.toLowerCase();
return sports.filter(function(sport) {
return (sport.name.toLowerCase().indexOf(filterBy) > -1) || sport.leagues.some(function(league) {
return league.name.toLowerCase().indexOf(filterBy) > -1;
});
});
};
});
sportsLeagues.html
<div ng-repeat="sport in sportsFilter = (sports | myFilter:query)">
<strong>{{sport.name}}</strong>
<div ng-repeat="league in sport.leagues | filter: { name:query }">
<div>{{league.name}} </div>
</div>
</div>
Make another filter for the leagues that returns all the leagues if their sportĀ“s name contains the query otherwise filters it normally:
myApp.filter('leaguesFilter', function () {
return function (leagues, filterBy) {
if (!leagues || !filterBy) return leagues;
filterBy = filterBy.toLowerCase();
if (leagues[0] && leagues[0].sport.name.toLowerCase().indexOf(filterBy) > -1) return leagues;
else return leagues.filter(function (league) {
return league.name.toLowerCase().indexOf(filterBy) > -1;
});
};
});
Demo: http://plnkr.co/edit/0vYKtBETxDx1tD3udO9Z?p=preview
I am ordering a my data and its working all correcty except some fields are empty or have no value. When ordered these empty field come up first. For example when ordering numbers we would get a huge empty list before getting the "0"-values.
I am doing it like thise:
ng-click="predicate = 'name'; reverse=!reverse"
and
ng-repeat="name in names | orderBy:predicate:reverse"
JSFiddle: http://jsfiddle.net/JZuCX/1/
Is there an easy elegant way to fix this? I want the empty fields to come last, no matter what.
How about this for sorting strings:
item in (items|orderBy:['!name', 'name'])
The advantage (apart from being more concise) is it sorts null & undefined with the blank strings.
In my case I wanted the blanks & nulls & undefineds together at the top (nulls and undefineds by default sort to the bottom), so I used:
item in (items|orderBy:['!!name', 'name'])
I'd write a filter that takes items with empty name from ordered array and places them at the end:
<li ng-repeat="item in (items|orderBy:'name'|emptyToEnd:'name')">{{item.name}}</li>
Code might look like this:
.filter("emptyToEnd", function () {
return function (array, key) {
if(!angular.isArray(array)) return;
var present = array.filter(function (item) {
return item[key];
});
var empty = array.filter(function (item) {
return !item[key]
});
return present.concat(empty);
};
});
Working example.
By the way, your fiddle doesn't contain any relevant code. Did you use the wrong link?
Update 2:
Your fiddle with my filter.
Down here! :D
This solution extends the normal functionality of the angularJs orderBy filter to take a third argument specifying whether or not to invert the normal sorting of null and undefined values. It observes the property names it is passed (not just one), and doesn't iterate over items a second as some of the other solutions do. It's used like this:
<li ng-repeat="item in (items|orderBy:'name':false:true)">{{item.name}}</li>
I found a bunch of threads, some not directly about orderBy, and compiled their techniques plus a couple bits of my own into this:
angular.module('lib')
.config(['$provide', function ($provide) {
$provide.decorator('orderByFilter', ['$delegate', '$parse', function ($delegate, $parse) {
return function () {
var predicates = arguments[1];
var invertEmpties = arguments[3];
if (angular.isDefined(invertEmpties)) {
if (!angular.isArray(predicates)) {
predicates = [predicates];
}
var newPredicates = [];
angular.forEach(predicates, function (predicate) {
if (angular.isString(predicate)) {
var trimmed = predicate;
if (trimmed.charAt(0) == '-') {
trimmed = trimmed.slice(1);
}
var keyFn = $parse(trimmed);
newPredicates.push(function (item) {
var value = keyFn(item);
return (angular.isDefined(value) && value != null) == invertEmpties;
})
}
newPredicates.push(predicate);
});
predicates = newPredicates;
}
return $delegate(arguments[0], predicates, arguments[2]);
}
}])
}]);
To use this code verbatim, be to specify 'lib' as a dependency for your app.
Credits to:
$parse
[nullSorter].concat(originalPredicates)
decorator pattern
I don't believe there's an "out of the box" solution for this. I could easily be wrong.
Here's my attempt at a solution using a function as the predicate:
ng-repeat="name in names | orderBy:predicate"
Inside your controller:
$scope.predicate = function(name) {
return name === '' ? 'zzzzzzz' : !name;
/* The 'zzzzzz' forces the empty names to the end,
I can't think of a simpler way at the moment. */
}
In addition to the solution of Klaster_1, add an extra parameter to make the filter more generic:
http://jsfiddle.net/Zukzuk/JZuCX/27/
Implementation
<tr ng-repeat="name in (names | orderBy:predicate:reverse | orderEmpty:'name':'toBottom')">
Filter
.filter('orderEmpty', function () {
return function (array, key, type) {
var present, empty, result;
if(!angular.isArray(array)) return;
present = array.filter(function (item) {
return item[key];
});
empty = array.filter(function (item) {
return !item[key]
});
switch(type) {
case 'toBottom':
result = present.concat(empty);
break;
case 'toTop':
result = empty.concat(present);
break;
// ... etc, etc ...
default:
result = array;
break;
}
return result;
};
});
Thnx Klaster_1!
Sorting, and reverse sorting, using a variable sort column, and keeping the undefined at the bottom, even below the negative values
I love the elegance of Sean's answer above! I needed to give my users the ability to choose the column to sort on, and choice of sort direction, but still require the undefined's to fall to the bottom, even if there are negative numbers.
The key insight from Sean that fixes negative numbers is !!. Use '!'+predicate if you are doing forward sorting and '!!'+predicate if you are doing reverse sorting.
The snippet below demonstrates this. By the way, I have put the variables that set the predicate (choice of propery to sort on) and reverse inside an object ("d") just so that we don't get weird scope issues. You may not need the "d."s in your environment.
Moreover you would probably want to use something better than my crappy buttons at the bottom of the page to control your sort predicate and direction. However this keeps the key parts of the code easy to read.
function mainController($scope) {
$scope.userArray = [
{ name: "Don", age: 20 },
{ name: "Bob", age: 30, height: 170 },
{ name: "Abe", age: 40, height: 160 },
{ name: "Zoe", age: 70 },
{ age: 70, height: 155 },
{ name: "Shorty",age:45,height: -200},
{ name: "TwinkleInEye", age: -1, height: 152 }
]
$scope.d = {}; // Create an object into which info can be stored and not trashed by Angular's tendency to add scopes
$scope.d.predicate = "name"; // This string is the name of the property on which to sort
$scope.d.reverse = false; // True means reverse the sort order
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<body ng-app="" ng-controller="mainController">
<div ng-repeat="user in (userArray | orderBy: (d.reverse ?['!!'+d.predicate,d.predicate]:['!'+d.predicate,d.predicate]) : d.reverse)">
Name {{ user.name }} : Age {{ user.age }} : Height {{ user.height }}
</div>
<br/>
<button ng-click="d.predicate='name';">Name</button>
<button ng-click="d.predicate='age';">Age</button>
<button ng-click="d.predicate='height';">Height</button> Currently: {{d.predicate}}
<br/> Leave undefined at bottom, but otherwise:
<button ng-click="d.reverse= !d.reverse;">Reverse</button> Currently: {{d.reverse}}
</body>
#Klaster_1 was really on to something but as soon as I needed a nested value the filter stopped working. Also, if I was reverse ordering I still wanted my null values to show up before 0. I added $parse to take care of the nested keys and added a reverse parameter to I knew when to put the null values at the top.
.filter("emptyToEnd", function ($parse) {
return function (array, key, reverse) {
if(!angular.isArray(array)) return;
var keyFn = $parse(key);
var present = [];
var empty = [];
angular.forEach(array, function(item){
var val = keyFn(item);
if(angular.isUndefined(val) || val === null) {
empty.push(item);
} else {
present.push(item);
}
});
if (reverse) {
return present.concat(empty);
} else {
return empty.concat(present);
}
};
});
I don't know why other answer suggest to put the null value records at the bottom, If I want to sort normally, means in ASC order all the null on top and in DESC order all the nulls go to bottom, I tried other answers here but could not helped me so change the code to convert the null to '' in my array and it works now smooth like this:
$scope.convertNullToBlank = function (array) {
for (var i = 0; i < array.length; i++) {
if (array[i].col1 === null)
array[i].col1 = '';
if (array[i].col2 === null)
array[i].col2 = '';
}
return array;
}
I created a gist with an alternative filter based on the previous solutions:
https://gist.github.com/360disrupt/1432ee1cd1685a0baf8967dc70ae14b1
The filter extends the existing angular filter:
angular.module 'tsd.orderByEmptyLast', []
.filter 'orderByEmptyLast', ($filter) ->
return (list, predicate, reverse)->
orderedList = $filter('orderBy')(list, if reverse then ['!' + predicate, '-' + predicate] else ['!' + predicate, predicate] )
return orderedList
On newer angular versions you might need to include orderByFilter instead of using $filter
angular.module 'tsd.orderByEmptyLast', ['orderByFilter']
.filter 'orderByEmptyLast', () ->
return (list, predicate, reverse)->
orderedList = orderByFilter(list, if reverse then ['!' + predicate, '-' + predicate] else ['!' + predicate, predicate] )
return orderedList