Sorting dropdown alphabetically in AngularJS - angularjs

I'm populating a dropdown through the use of ng-options which is hooked to a controller that in turn is calling a service. Unfortunately the data coming in is a mess and I need to be able to sort it alphabetically.
You figure that something like $.sortBy would do it but unfortunately it didn't do jack. I know I can sort it via javascript with a helper method function asc(a,b) or something like that but I refuse to believe that there is not cleaner way of doing this plus I don't want to bloat the controller with helper methods. It is something so basic in principle so I don't understand why AngularJS doesn't have this.
Is there a way of doing something like $orderBy('asc')?
Example:
<select ng-option="items in item.$orderBy('asc')"></select>
It would be extremely useful to have options in orderBy so you can do whatever you want, whenever you usually try to sort data.

Angular has an orderBy filter that can be used like this:
<select ng-model="selected" ng-options="f.name for f in friends | orderBy:'name'"></select>
See this fiddle for an example.
It's worth noting that if track by is being used it needs to appear after the orderBy filter, like this:
<select ng-model="selected" ng-options="f.name for f in friends | orderBy:'name' track by f.id"></select>

You should be able to use filter: orderBy
orderBy can accept a third option for the reverse flag.
<select ng-option="item.name for item in items | orderBy:'name':true"></select>
Here item is sorted by 'name' property in a reversed order.
The 2nd argument can be any order function, so you can sort in any rule.
#see http://docs.angularjs.org/api/ng.filter:orderBy

var module = angular.module("example", []);
module.controller("orderByController", function ($scope) {
$scope.orderByValue = function (value) {
return value;
};
$scope.items = ["c", "b", "a"];
$scope.objList = [
{
"name": "c"
}, {
"name": "b"
}, {
"name": "a"
}];
$scope.item = "b";
});
http://jsfiddle.net/Nfv42/65/

For anyone who wants to sort the variable in third layer:
<select ng-option="friend.pet.name for friend in friends"></select>
you can do it like this
<select ng-option="friend.pet.name for friend in friends | orderBy: 'pet.name'"></select>

Related

Angular 1.5.x - passing a variable to a built-in filter

I'm trying to filter data based on the select and input fields. Here is part of my code:
<select ng-change="students.changeValue(changeFilter)" ng-model="changeFilter">
<option ng-selected="true" value="name">Name</option>
<option value="age">Age</option>
</select>
<input ng-model="searchPhrase" />
name and age are example keys that I have. Here is my data generator structure:
<div ng-class="{breakLine: $index % 3 === 0}"
class="student-item col-md-4"
ng-repeat="s in students.studentsList | filter:{searchFilter: searchPhrase}">
The searchFilter is supposed to be a key that is set from a variable but it doesn't work. If I make there something like: filter:{name: searchPhrase} then it works because I have such keys in my data structures.
Here is a part of the controller:
.controller('StudentsListCtrl', ['$scope', function($scope) {
$scope.searchFilter = '';
this.changeValue = function(val) {
console.log(val); ---> gives key or age on change
$scope.searchFilter = val;
}
So when I manually write e.g.: | filter:{name: searchPhrase} then it works. But when I pass 'name' (i.e. the key) in a variable like: | filter:{searchFilter: searchPhrase} then it's broken...
How can I fix this?
You should use the last option described in filter documentation for the expression:
function(value, index, array): A predicate function can be used to write arbitrary filters. The function is called for each element of the array, with the element, its index, and the entire array itself as arguments.
In the controller, you define the predicate, e.g.
$scope.personFilter = function(person) {
// return true if this person should be displayed
// according to the defined filter
}
and use it in the ng-repeat filter, e.g.
<ul>
<li ng-repeat="p in persons | filter:personFilter">{{p.name}} ({{p.age}})</li>
</ul>
See the plunker for a demo. Note I choose age as a string to simplify the predicate function. You can refine it according to your needs.
Write a function in your $scope to generate the object that is passed to the filter:
$scope.getFilterObject = function() {
var filterObject = {};
filterObject[$scope.searchFilter] = $scope.searchPhrase;
return filterObject;
}
Use it as the argument in the filter:
ng-repeat="s in students.studentsList | filter:getFilterObject()"

Adding filter to ng-repeat

I'm currently making a front-end with Angular.
I have a JSON file like following:
{
"groups": [
group1: {
"part":1
},
group2: {
"part":2
}
]
}
And I have lists like following:
<li ng-class="{active: section >= {{group.number}}}" ng-bind="group.title" ng-repeat="group in groups" ></li>
Let's say there are 100 groups in my JSON file. If I want to only show groups with "part":1, how do I add this filter in ng-repeat?
You can pass an object to filter with the key/value you want to filter on:
ng-repeat="group in groups | filter:{part:1}"
try this
ng-repeat="group in groups | filter:{'part': 1}:true"
from official documentation
In HTML Template Binding
{{ filter_expression | filter : expression :
comparator}}
for comparator value if its true
true: A shorthand for function(actual, expected) { return
angular.equals(actual, expected)}. This is essentially strict
comparison of expected and actual.
this gives you the exact match
Consider also passing a function rather than Object into filter (which may work this time, but not all things are easily expressible in a readable fashion directly in the view):
ng-repeat="group in groups | filter:functionOnScope"
The | pipe operates on the thing to the left groups, so filter is a function whose first argument receives groups and whose subsequent arguments appear after the :. You could visualize a | b:c:d | e as e(b(a,c,d)) - once I realized that I used filters more for simple things.
So the second argument filter receives is a predicate (function that takes in something and returns true or false to operate on each element - like a SQL WHERE clause) inside groups. Filters are super useful - if you have quick logic or transformations you want to do in the view (and you don't need to test it) then they can make your controllers and directives more succinct. (So instead of ng-if="collection[collection.length - 1].length > 0" you could write ng-if="collection | last | some", which is much more readable.)
If you have complicated logic, it may be better to put in a controller or directive instead of the view (this is also easier to unit test that way if you care about it) - if it's in the view you need something like PhantomJS at a minimum to emulate the DOM. Assuming you bound some dynamicallySelectedPart on the $scope to 1, 2, etc. maybe as an ng-model on a <select /> so the user can select it, then you can just write this to keep it dynamically up-to-date.
$scope.functionOnScope = function (elementInGroups) {
// Maybe do a check like:
// if ($scope.dynamicallySelectedPart === elementInGroups.part) {
return true;
// }
// Some other logic...
return false;
};
Your JSON looks malformed in that you have an array with key-value pairs.
Below is some code that should work. I am using the Controller ViewAs syntax.
HTML
<div ng-app="MyApp">
<div ng-controller="MyController as me">
{{me.greeting}}
<ul>
<li ng-repeat="group in me.groups | filter:{'part': 1}:true">
{{group}}
</li>
</ul>
</div>
JS
var myApp = angular.module('MyApp',[]);
myApp.controller('MyController', function() {
this.greeting = 'Hola!';
this.groups = [ {id: 'group1', "part":1 }, {id: 'group2', "part":2 } ];
});
Code Pen Here

Angularjs add properties to model with a filter?

I have a timestamp which I want to format in a specific way. For example:
Today at 4:57:30pm (in a few seconds)
Now, I could output the timestamp with a filter and have the filter simply output the html.
However, I'd rather keep the html tags/classes in the template, and somehow have the filter modify the original data with some new properties I can output after the filter call in the template.
For example, I am using ng-repeat to go over an array of objects that look like this:
{timestamp: 123345236, something: 'asdf'}
I want to run a filter to make it look like this:
{timestamp: 123345236, today: 'Today at ', time: '4:57:30pm', relative_time: '(in a few seconds)'}
Then I can reference those new keys to output in my template.
Is this possible?
I think you need something like this:
app.filter("myFilter",function(){
return function(input) {
var arr = [];
angular.forEach(input, function(value, key){
value.newCustomProperty = "Custom property" + key;
this.push(value);
}, arr);
return arr;
}
});
Apply the filter to ng-repeat:
<div ng-repeat="i in data | myFilter">
</div>
DEMO

angular js - using filter in select, not returning the correct set

I'm trying to filter a selection list:
<div ng-controller="Ctrl">
<select ng-model="targetToMap" required="true"
ng-options="code.instructions_short for code in targetCodes |filter:targetCodes.environment=2"
></select>
<div>
var app = angular.module('app', []);
function Ctrl($scope) {
$scope.targetCodes = [
{"fid":"1","environment":"1","instructions_short":"Script1.1"},
{"fid":"2","environment":"1","instructions_short":"Script1.2"},
{"fid":"3","environment":"1","instructions_short":"Script1.3"},
{"fid":"4","environment":"1","instructions_short":"Script1.4"},
{"fid":"5","environment":"2","instructions_short":"Script2.1"},
{"fid":"6","environment":"2","instructions_short":"Script2.2"},
{"fid":"7","environment":"2","instructions_short":"Script2.3"},
{"fid":"8","environment":"2","instructions_short":"Script2.4"}
];
}
For some reason the filter appears to applying to both FID and ENVIRONMENT, so it returns a list including fid=2 as well as the 4 records where environment=2. I put the filter inline to simplify the code. What's wrong with this picture?
A jsfiddle can be found here: jsfiddle
Looks to me like you are using the wrong syntax for filter.
Try replacing your filter with
| filter: {environment: '2'}
This will look for only objects in your array that have the property environment set to 2. Currently I don't know why it is doing what it's doing, but targetCodes doesn't have the environment property so the filter doesn't seem to make a lot of sense in general, even if it was using correct syntax.
Edit
Quick experiment shows that filtering on the original
targetCodes.environment=2
actually sets targetCodes.environment to 2, and also evaluates to 2. So this was equivalent to filtering with
| filter: 2
To filter properties, you should use:
filter: { name: 'val'}
You can see it in the first comment on the filter docs (Yes, its not necessarily "documented")
So, in your code, should be:
<select ng-model="targetToMap" required="true" ng-options="code.instructions_short for code in targetCodes | filter: {environment: '2'}">
Fiddle
You can also try something like this:
HTML:
<div ng-controller="Ctrl">
<select ng-model="targetToMap" required="true" ng-options="code.instructions_short for code in targetCodes">
<option value="">-- Select Instructions --</option>
</select>
</div>
JavaScript:
var app = angular.module('app', []);
function Ctrl($scope) {
var allTargetCodes =[
{"fid":"1","environment":"1","instructions_short":"Script1.1"},
{"fid":"2","environment":"1","instructions_short":"Script1.2"},
{"fid":"3","environment":"1","instructions_short":"Script1.3"},
{"fid":"4","environment":"1","instructions_short":"Script1.4"},
{"fid":"5","environment":"2","instructions_short":"Script2.1"},
{"fid":"6","environment":"2","instructions_short":"Script2.2"},
{"fid":"7","environment":"2","instructions_short":"Script2.3"},
{"fid":"8","environment":"2","instructions_short":"Script2.4"}
];
function filterTargetCodes() {
$scope.targetCodes = [];
angular.forEach(allTargetCodes, function(row) {
if (row.environment != "2") {
return;
}
$scope.targetCodes.push(row);
});
};
filterTargetCodes();
}
Example

Table filter by predicate

I made a jsfiddle to show what is my problem.
The fisrt part is working in a partial way. See line number 15. I put the predicate in the filter (predicate is l_name) by hand and is working. The table is filtered by Last Name column.
<tr ng-repeat="item in items | filter:{l_name:myInput}">
The second part of the sample is not working when I use the select (model named mySelect2) to choose the predicate where I'm going to filter (see line number 36).
What I'm trying to do is use the select to choose the column by predicate and the input to filter in that column.
<tr ng-repeat="item in items | filter:{mySelect2:myInput2}">
Am I missing something or the binding of the select (mySelect2) must update the filter on the table?
Thanks for the help!
PS: type jo in the input.
Here's a fiddle with some options: http://jsfiddle.net/jgoemat/tgKkD/1/
Option 1 - Search on multiple fields
You can use an object on your model ('search' here) as your filter and separate input boxes for l_name and f_name. This allows you not only to filter on either, but filter on both:
any: <input ng-model="search.$"/><br/>
l_name: <input ng-model="search.l_name"/><br/>
f_name: <input ng-model="search.f_name"/><br/>
<!-- skipping code -->
<tr ng-repeat="item in items|filter:search">
Option 2 - Use a function on your controller
The built-in filter can take a function as an argument that should return true if the object should be included. This function takes the object to be filtered as its only argument and returns true if it should be included. Html:
<tr ng-repeat="item in items|filter:filterFunc">
controller function:
$scope.filterFunc = function(obj) {
// property not specified do we want to filter all instead of skipping filter?
if (!$scope.mySelect)
return obj;
if (obj[$scope.mySelect].toLowerCase().indexOf($scope.myInput.toLowerCase()) >= 0)
return obj;
return false;
};
Option 3 - Create a custom filter
This filter function will take the whole list as an argument and return the filtered list. This does require you to create an angular module and specify it in the ng-app tag like ng-app="MyApp"Html:
<tr ng-repeat="item in items|MyFilter:mySelect:myInput">
Code:
var app = angular.module('MyApp', []);
app.filter('MyFilter', function() {
return function(list, propertyName, value) {
console.log('MyFilter(list, ', propertyName, ', ', value, ')');
// property not specified do we want to filter all instead of skipping filter?
if (!propertyName)
return list;
var newList = [];
var lower = value.toLowerCase();
angular.forEach(list, function(v) {
if (v[propertyName].toLowerCase().indexOf(lower) >= 0)
newList.push(v);
});
return newList;
}
});
Option 4: ng-show
The built-in filter filter expressions don't let you use any expression, but ng-show does so you can just limit visible items like so:
<tr ng-show="item[mySelect].toLowerCase().indexOf(myInput.toLowerCase()) >= 0 || !mySelect" ng-repeat="item in items">
I think option 1 is easy and flexible. If you prefer your drop-down + field UI then I think option 3 is the most useful, and you can re-use it as a dependency in other apps like this:
var app = angular.module("NewApp", ["MyApp"]);
I would just name it something better like 'filterByNamedProperty'. Option 2 is easy but it is tied to your controller. Option 4 is messy and I wouldn't use it.
What about using a custom filter? Users concatenate the property with the criteria (e.g. last:jo). In the filter, split on the colon, and use the first part as the property name and the second part as the criteria.
You may pass scope variables to your filters:
<tr ng-repeat="item in items | filter:myScopeVariable">
This means that you may define your filter object in controller and it will be used by the filter:
$scope.$watch('mySelect2', function(val){
$scope.myScopeVariable = {};
$scope.myScopeVariable[val] = $scope.myInput2;
});
$scope.$watch('myInput2', function(val){
$scope.myScopeVariable = {};
$scope.myScopeVariable[$scope.mySelect2] = $scope.myInput2;
});
Demo Fiddle

Resources