Angular - Filter an array - angularjs

I was wondering how I can filter an array to show only results if a property in the array is empty. I have tried the following but to no avail:
<tr ng-repeat="performanceOrder in performanceOrders | filter: salesId.length === 0">
I only want to show results if salesId is empty, is this possible?
Edit
salesId is a property of performanceOrder
performanceOrder: {
salesId: "S273626",
status: "Open",
...
}

With ngShow:
<tr ng-show="!salesId.length" ng-repeat="performanceOrder in performanceOrders">
With ngIf:
<tr ng-if="!salesId.length" ng-repeat="performanceOrder in performanceOrders">

You can use a function inside the filter like this:
<tr ng-repeat="performanceOrder in performanceOrders | filter: myFilter()>
Then, in your corresponding controller, you can do something like:
$scope.myFilter = function (val) {
return val.salesId.length === 0;
}

Related

Filter on column and also sort in descending order in AngularJS

How can I sort in descending order but also filter on column in AngularJS? I have the following code that will default to the most recent timestamp.
<tr ng-repeat="item in log | orderBy:'-timestamp' ">
<td>{{item.timestamp | date: 'MM/dd/yyyy HH:MM'}}</td>
<td>{{item.Action | removeHTMLTags | strip }}</td>
<td>{{item.AO}}</td>
</tr>
I also have the following js code that will work for filtering on the column header.
$scope.reverseOrder = true;
$scope.sortField = '';
$scope.sortBy = function(sortField) {
$scope.reverseOrder = ($scope.sortField == sortField) ? !$scope.reverseOrder : false;
$scope.sortField = sortField;
}
<tr ng-repeat="item in log | orderBy:sortField:reverseOrder">
<td>{{item.timestamp | date: 'MM/dd/yyyy HH:MM'}}</td>
<td>{{item.Action | removeHTMLTags | strip }}</td>
<td>{{item.AO}}</td>
</tr>
I would like to both filter on the column but also default to show the most recent timestamps on top.
I tried the following but it did not work.
ng-repeat="item in log | orderBy:sortField:reverseOrder | orderBy: '-timestamp'"
In order to understand why your code isn't working you must understand first, how the orderBy filter works in angularjs. In your ng-repeat the orderBy filter has two parameters sortField and reverseOrder. For them to work you must have a controller that has two $scope variables named respectively sortField and reverseOrder. The first parameter is to determine which key should orderBy use to order your array and the second one is a boolean that specifies would the order be reversed or not.
In my attempt to reproduce your example, I took the liberty of using unix timestamps in my logs array. So in the controller,
$scope.searchText = "";
$scope.sortField = "timestamp";
$scope.reverseOrder = false;
$scope.logs = [
{
timestamp : 2147450400000,
Action : 'bal sal',
AO : 'abcd'
},
{
timestamp : 979840800000,
Action : 'bal sal2',
AO : 'abcd2'
},
{
timestamp : 1579370400000,
Action : 'bal sal2',
AO : 'abcd2'
}
]
$scope.sortBy = function(sortField) {
$scope.reverseOrder = ($scope.sortField === sortField) ? !$scope.reverseOrder : false;
$scope.sortField = sortField;
};
Also in the example you provided, they passed the column name via a function. So in your html you need to have something like this.
<input type="text" ng-model="searchText">
<table>
<thead>
<th ng-click="sortBy('timestamp')">Time Stamp</th>
<th ng-click="sortBy('Action')">Action</th>
<th ng-click="sortBy('AO')">AO</th>
</thead>
<tr ng-repeat="item in logs | orderBy:sortField:reverseOrder| filter:searchText">
<td>{{item.timestamp | date :'MM/dd/yyyy HH:MM'}}</td>
<td>{{item.Action}}</td>
<td>{{item.AO}}</td>
</tr>
</table>
Hope this clears your confusions.

OrderBy array in AngularJS according to search

I'm still stuck with a OrderBy issue. Data comes from $http, and looks like this:
[
{
"number":1,
"timetable": [
{
"name":"Station1",
"time":"2016-05-18T18:14:00.000Z"
},
{
"name":"Station2",
"time":"2016-05-18T18:18:00.000Z"
}
]
},
{
"number":2,
"timetable": [
{
"name":"Station1",
"time":"2016-05-18T18:24:00.000Z"
},
{
"name":"Station2",
"time":"2016-05-18T18:28:00.000Z"
}
]
}
]
So what I need is to view rows where name is for example Station2, and I need them in order by time. The rows aren't ordered by time, nor by number. Amount of timetable rows varies, so row numbers don't help either. Is it possible to order them inside ng-repeat, in style of "OrderBy time where name='Station2' "?
EDIT:
At the moment I'm showing the results without any ordering, only with filtering. Current PHP:
<tr ng-repeat="x in rows | limitTo:5">
<td>
<a href="aikataulu.php?n={{x.number}}">
<span class="label label-primary line-{{x.lineID}}">{{x.lineID}}</span> {{x.number}}
</a>
</td>
<td>
<span ng-repeat="y in x.timetable | limitTo:-1">{{y.name}}</span> //This is for showing the destination
</td>
<td>
<span ng-repeat="y in x.timetable | filter:{'name':'<?php echo $as;?>'}: true | limitTo:1">{{y.time | date:'HH:mm'}}</span>
</td>
</tr>
$as is the station to be shown. So, now the order of the list comes straight from the JSON order, so it varies a lot.
You can use the comparator as an additional argument for the filter.
So you would expand your code to:
<span ng-repeat="y in x.timetable | filter:{'name':'<?php echo $as;?>'}: true : myComparator | limitTo:1">{{y.time | date:'HH:mm'}}</span>
You need a regexp to see if the compared string values are dates and you can do something similar to:
$scope.myComparator= function (a, b) {
// regex to see if the compared values are dates
var isDate = /(-(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)-)/g;
// sorting dates
if (isDate.test(a.value)) {
var aDate = new Date(a.value), bDate = new Date(b.value);
return aDate.getTime() < bDate.getTime() ? -1 : 1
}
// default sorting
return a.index < b.index ? -1 : 1
}
A thing worth noting is you would need a different regular expression to find your date format. Something along the lines of the following might be sufficient:
/(\d{4})-(\d{2})-/g
NOTE: This is untested code and serves only as a guide to the right approach. Make sure your version of AngularJS supports the comparator as a filter argument.
You can order an array of objects by one of their properties with a filter function like this one:
angular.module('yourApp').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;
};
}
);
Use it like this
<div ng-repeat="elem in data | orderObjectBy:'number'">
// ...
</div>

Bind filter in angularjs template [duplicate]

My goal is to apply a formatting filter that is set as a property of the looped object.
Taking this array of objects:
[
{
"value": "test value with null formatter",
"formatter": null,
},
{
"value": "uppercase text",
"formatter": "uppercase",
},
{
"value": "2014-01-01",
"formatter": "date",
}
]
The template code i'm trying to write is this:
<div ng-repeat="row in list">
{{ row.value | row.formatter }}
</div>
And i'm expecting to see this result:
test value with null formatter
UPPERCASE TEXT
Jan 1, 2014
But maybe obviusly this code throws an error:
Unknown provider: row.formatterFilterProvider <- row.formatterFilter
I can't immagine how to parse the "formatter" parameter inside the {{ }}; can anyone help me?
See the plunkr http://plnkr.co/edit/YnCR123dRQRqm3owQLcs?p=preview
The | is an angular construct that finds a defined filter with that name and applies it to the value on the left. What I think you need to do is create a filter that takes a filter name as an argument, then calls the appropriate filter (fiddle) (adapted from M59's code):
HTML:
<div ng-repeat="row in list">
{{ row.value | picker:row.formatter }}
</div>
Javascript:
app.filter('picker', function($filter) {
return function(value, filterName) {
return $filter(filterName)(value);
};
});
Thanks to #karlgold's comment, here's a version that supports arguments. The first example uses the add filter directly to add numbers to an existing number and the second uses the useFilter filter to select the add filter by string and pass arguments to it (fiddle):
HTML:
<p>2 + 3 + 5 = {{ 2 | add:3:5 }}</p>
<p>7 + 9 + 11 = {{ 7 | useFilter:'add':9:11 }}</p>
Javascript:
app.filter('useFilter', function($filter) {
return function() {
var filterName = [].splice.call(arguments, 1, 1)[0];
return $filter(filterName).apply(null, arguments);
};
});
I like the concept behind these answers, but don't think they provide the most flexible possible solution.
What I really wanted to do and I'm sure some readers will feel the same, is to be able to dynamically pass a filter expression, which would then evaluate and return the appropriate result.
So a single custom filter would be able to process all of the following:
{{ammount | picker:'currency:"$":0'}}
{{date | picker:'date:"yyyy-MM-dd HH:mm:ss Z"'}}
{{name | picker:'salutation:"Hello"'}} //Apply another custom filter
I came up with the following piece of code, which utilizes the $interpolate service into my custom filter. See the jsfiddle:
Javascript
myApp.filter('picker', function($interpolate ){
return function(item,name){
var result = $interpolate('{{value | ' + arguments[1] + '}}');
return result({value:arguments[0]});
};
});
One way to make it work is to use a function for the binding and do the filtering within that function. This may not be the best approach: Live demo (click).
<div ng-repeat="row in list">
{{ foo(row.value, row.filter) }}
</div>
JavaScript:
$scope.list = [
{"value": "uppercase text", "filter": "uppercase"}
];
$scope.foo = function(value, filter) {
return $filter(filter)(value);
};
I had a slightly different need and so modified the above answer a bit (the $interpolate solution hits the same goal but is still limited):
angular.module("myApp").filter("meta", function($filter)
{
return function()
{
var filterName = [].splice.call(arguments, 1, 1)[0] || "filter";
var filter = filterName.split(":");
if (filter.length > 1)
{
filterName = filter[0];
for (var i = 1, k = filter.length; i < k; i++)
{
[].push.call(arguments, filter[i]);
}
}
return $filter(filterName).apply(null, arguments);
};
});
Usage:
<td ng-repeat="column in columns">{{ column.fakeData | meta:column.filter }}</td>
Data:
{
label:"Column head",
description:"The label used for a column",
filter:"percentage:2:true",
fakeData:-4.769796600014472
}
(percentage is a custom filter that builds off number)
Credit in this post to Jason Goemaat.
Here is how I used it.
$scope.table.columns = [{ name: "June 1 2015", filter: "date" },
{ name: "Name", filter: null },
] etc...
<td class="table-row" ng-repeat="column in table.columns">
{{ column.name | applyFilter:column.filter }}
</td>
app.filter('applyFilter', [ '$filter', function( $filter ) {
return function ( value, filterName ) {
if( !filterName ){ return value; } // In case no filter, as in NULL.
return $filter( filterName )( value );
};
}]);
I improved #Jason Goemaat's answer a bit by adding a check if the filter exists, and if not return the first argument by default:
.filter('useFilter', function ($filter, $injector) {
return function () {
var filterName = [].splice.call(arguments, 1, 1)[0];
return $injector.has(filterName + 'Filter') ? $filter(filterName).apply(null, arguments) : arguments[0];
};
});
The newer version of ng-table allows for dynamic table creation (ng-dynamic-table) based on a column configuration. Formatting a date field is as easy as adding the format to your field value in your columns array.
Given
{
"name": "Test code",
"dateInfo": {
"createDate": 1453480399313
"updateDate": 1453480399313
}
}
columns = [
{field: 'object.name', title: 'Name', sortable: 'name', filter: {name: 'text'}, show: true},
{field: "object.dateInfo.createDate | date :'MMM dd yyyy - HH:mm:ss a'", title: 'Create Date', sortable: 'object.dateInfo.createDate', show: true}
]
<table ng-table-dynamic="controller.ngTableObject with controller.columns" show-filter="true" class="table table-condensed table-bordered table-striped">
<tr ng-repeat="row in $data">
<td ng-repeat="column in $columns">{{ $eval(column.field, { object: row }) }}</td>
</tr>
</table>
I ended up doing something a bit more crude, but less involving:
HTML:
Use the ternary operator to check if there is a filter defined for the row:
ng-bind="::data {{row.filter ? '|' + row.filter : ''}}"
JS:
In the data array in Javascript add the filter:
, {
data: 10,
rowName: "Price",
months: [],
tooltip: "Price in DKK",
filter: "currency:undefined:0"
}, {
This is what I use (Angular Version 1.3.0-beta.8 accidental-haiku).
This filter allows you to use filters with or without filter options.
applyFilter will check if the filter exists in Angular, if the filter does not exist, then an error message with the filter name will be in the browser console like so...
The following filter does not exist: greenBananas
When using ng-repeat, some of the values will be undefined. applyFilter will handle these issues with a soft fail.
app.filter( 'applyFilter', ['$filter', '$injector', function($filter, $injector){
var filterError = "The following filter does not exist: ";
return function(value, filterName, options){
if(noFilterProvided(filterName)){ return value; }
if(filterDoesNotExistInAngular(filterName)){ console.error(filterError + "\"" + filterName + "\""); return value; }
return $filter(filterName)(value, applyOptions(options));
};
function noFilterProvided(filterName){
return !filterName || typeof filterName !== "string" || !filterName.trim();
}
function filterDoesNotExistInAngular(filterName){
return !$injector.has(filterName + "Filter");
}
function applyOptions(options){
if(!options){ return undefined; }
return options;
}
}]);
Then you use what ever filter you want, which may or may not have options.
// Where, item => { name: "Jello", filter: {name: "capitalize", options: null }};
<div ng-repeat="item in items">
{{ item.name | applyFilter:item.filter.name:item.filter.options }}
</div>
Or you could use with separate data structures when building a table.
// Where row => { color: "blue" };
// column => { name: "color", filter: { name: "capitalize", options: "whatever filter accepts"}};
<tr ng-repeat="row in rows">
<td ng-repeat="column in columns">
{{ row[column.name] | applyFilter:column.filter.name:column.filter.options }}
</td>
</tr>
If you find that you require to pass in more specific values you can add more arguments like this...
// In applyFilter, replace this line
return function(value, filterName, options){
// with this line
return function(value, filterName, options, newData){
// and also replace this line
return $filter(filterName)(value, applyOptions(options));
// with this line
return $filter(filterName)(value, applyOptions(options), newData);
Then in your HTML perhaps your filter also requires a key from the row object
// Where row => { color: "blue", addThisToo: "My Favorite Color" };
// column => { name: "color", filter: { name: "capitalize", options: "whatever filter accepts"}};
<tr ng-repeat="row in rows">
<td ng-repeat="column in columns">
{{ row[column.name] | applyFilter:column.filter.name:column.filter.options:row.addThisToo }}
</td>
</tr>

AngularJS : How to filter one object attribute with passed argument

I have meet a problem, I try to filter the query result, I want to filter the item.type. The filter value is bounded on the input field.
I try the following code, but it doesn't work. How can I make it correct? Thanks.
Search: <input ng-model="query">
Current filter: {{query}}
<table>
<tr ng-repeat="item in items | filter : {type : 'query'}">
<td>{{item.type}}</td>
<td>{{item.host}}</td>
</tr>
</table>
Use a custom filter
$scope.filterByType = function(item) {
return item.type == "query";
}
And the use:
<tr ng-repeat="item in items | filter : filterByType">
Just remove the quotes:
filter : {type : query}
Demo
I try the following code, it will return all when no input in the query filter.
$scope.filterByType = function(item) {
if ($scope.query == undefined || $scope.query.length == 0) {
return item;
}
return item.type.toLowerCase().indexOf($scope.query.toString().toLowerCase()) >=0 ;
};

How to have or condition on a ng-repeat

How to have or condition on this repeat ?
I want to filter this tbody depending of a value
MY TABLE
<tbody ng-repeat="Fund in ListTask | filter:{CreationDate: manager.start}:true | filter:{DueDate: manager.end}:true | filter:{Beneficiary: currentNode.name}:true | filter:{Title: currentNode.name}:true | filter:{category: currentNode.name}:true">
<tr ng-dblclick="open(Fund.id)" data-toggle="modal" data-target="{{Fund.type}}">
<td>{{Fund.id}}</td>
<td>{{Fund.Beneficiary}}</td>
<td>{{Fund.Title}}</td>
<td>{{Fund.status}}</td>
<td>{{Fund.category}}</td>
<td>{{FormField.shortDate(Fund.CreationDate)}}</td>
<td>{{FormField.shortDate(Fund.DueDate)}}</td>
<td>{{Fund.Team}}</td>
</tr>
</tbody>
It seems you have to implement your own filter.
Example :
.filter('specialFilter', function() {
return function(funds,manager,currentNode) {
var filtered = [];
angular.forEach(funds, function(fund) {
if (fund.CreationDate ==manager.start|| fund.category==currentNode.name)
filtered.push(fund);
});
return filtered;
};
})
<tbody ng-repeat="Fund in ListTask | specialFilter:manager:currentNode"
To notice, if you have an important number of funds (>2000), it could be better for performances to build yourself a specific array of data filtered.

Resources