Adding filter to ng-repeat - angularjs

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

Related

AngularJS - Can't use filter to set variable in view

I'm using a filter to calculate values, but then I want to access them later on.
Here's a snippet of my code:
app.controller('CreateProposalCtrl', function() {
$scope.EstimatedCostItem = [];
});
-
app.filter('EstimatedValue', function() {
return function(input, arguments) {
...blah blah calculations
return calculations;
};
});
I'm not sure how the HTML should be presented.
This displays exactly what I want..BUT I need it to set a variable so I can access it somewhere else..
<span ng-repeat="foo in bar">
{{ Type.JobTypeID | EstimatedValue: form }}
</span>
I've tried:
<span ng-model="EstimatedCostItem[Type.JobTypeID]" ng-bind="Type.JobTypeID | EstimatedValue: form"></span>
And:
<span ng-bind="EstimatedCostItem[Type.JobTypeID]">{{ Type.JobTypeID | EstimatedValue: form }}</span>
And:
<span ng-init="EstimatedCostItem[Type.JobTypeID] = (Type.JobTypeID | EstimatedValue: form)"></span>
Nothing seems to set a variable. I'm stumped :(
The filter syntax only works within specific Angular expressions. Expressions that use plain JavaScript cannot use the foo | bar filter syntax, as it's not valid JavaScript.
What you could do is use $filter:
var value = $filter('EstimatedValue')(foo);
Remember to inject $filter into your controller.
With that said, this probably isn't the best use of a filter. Why not create a scope function that calculates and stores the value? Something like:
$scope.EstimatedValue = function(foo) {
var value = doSomeCalculations();
// store for usage elsewhere
this.estimatedValuesCache[foo] = val;
return val;
};

AngularJS: Display the first element of the response array

I currently have a form with two dynamic dropdowns (Location & Branch). When one of Location values is selected, Branch will automatically populate the corresponding branches with that location.
<select ng-model="formData.location"
ng-options="rg as rg.type for rg in region">
<option value="">Choose Location</option>
</select>
<select ng-model="formData.branches"
ng-options="c as c[formData.location.displayName] for c in formData.location.data | orderBy:'branch'">
<option value="">Choose Branch</option>
</select>
The Branch values are taken from this controller:
scope.metro = [
{"branch": "SM North EDSA", "alias": "northedsa"},
{"branch": "Trinoma", "alias": "trinoma"},
{"branch": "Robinsons Galleria", "alias": "robgalleria"},
// etc...
];
scope.region = [
{ type: 'Metro Manila', data:scope.metro, displayName:'branch', alias:'alias'},
{ type: 'Central Luzon', data:scope.central, displayName:'branch', alias:'alias'},
{ type: 'North Luzon', data:scope.north, displayName:'branch', alias:'alias'},
// etc...
];
Now, inside the form, on every option change in Branch, there will be a pre-generated code from the table in my database (assigned on each of its row), procured by ng-repeat like this:
<div ng-repeat="codes in response">
<span ng-if="((codes.branch == formData.branches.alias) && (codes.taken == 0))">
{{codes.code}}
</div>
My database table looks like this:
This works when it is left as it is (displaying 100 codes each iteration). But when I use a filter such as limitTo:1, I only get the first index of the row in the table of my database. What I need is to get the first element of the response array on every flip of Branch values.
For clearer explanation, this ng-repeat is done by having this function in my controller:
http.get("server/fetch.php").success(function(response){
scope.response = response;
// shuffleArray(scope.response);
}).error(function() {
scope.response = "error in fetching data";
});
I was told do this in a controller instead if I wanted to get the first element of each array, but I am not sure how to do that. I will post a plunker when I have the time. I just need this solve right now as I have deadlines to meet before the day ends.
I hope this question is all clear even without a plunker. Thanks in advance!
You could pipe multiple filters together, the first to filter based on branch, and next to limit the number of returned items:
<div ng-repeat="codes in response | filter: {branch: formData.branches.alias, taken: 0} | limitTo: 1">
{{codes.code}}
</div>
In this case it was possible to use the built-in filter filter since it allows specifying a predicate for multiple properties to match against with an AND condition. For any more complex condition, I recommend using a predicate function:
$scope.filterBy = function(a, b){
return function(item){
return item.foo === a || item.bar === b;
}
}
and use it like so:
<div ng-repeat="item in items | filter: filterBy('foo', 'bar')">

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

Sorting dropdown alphabetically in 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>

Custom sort function in ng-repeat

I have a set of tiles that display a certain number depending on which option is selected by the user. I would now like to implement a sort by whatever number is shown.
The code below shows how I've implemented it (by gettting/setting a value in the parent cards scope). Now, because the orderBy function takes a string, I tried to set a variable in the card scope called curOptionValue and sort by that, but it doesn't seem to work.
So the question becomes, how to I create a custom sort function?
<div ng-controller="aggViewport" >
<div class="btn-group" >
<button ng-click="setOption(opt.name)" ng-repeat="opt in optList" class="btn active">{{opt.name}}</button>
</div>
<div id="container" iso-grid width="500px" height="500px">
<div ng-repeat="card in cards" class="item {{card.class}}" ng-controller="aggCardController">
<table width="100%">
<tr>
<td align="center">
<h4>{{card.name}}</h4>
</td>
</tr>
<tr>
<td align="center"><h2>{{getOption()}}</h2></td>
</tr>
</table>
</div>
</div>
and controller :
module.controller('aggViewport',['$scope','$location',function($scope,$location) {
$scope.cards = [
{name: card1, values: {opt1: 9, opt2: 10}},
{name: card1, values: {opt1: 9, opt2: 10}}
];
$scope.option = "opt1";
$scope.setOption = function(val){
$scope.option = val;
}
}]);
module.controller('aggCardController',['$scope',function($scope){
$scope.getOption = function(){
return $scope.card.values[$scope.option];
}
}]);
Actually the orderBy filter can take as a parameter not only a string but also a function. From the orderBy documentation: https://docs.angularjs.org/api/ng/filter/orderBy):
function: Getter function. The result of this function will be sorted
using the <, =, > operator.
So, you could write your own function. For example, if you would like to compare cards based on a sum of opt1 and opt2 (I'm making this up, the point is that you can have any arbitrary function) you would write in your controller:
$scope.myValueFunction = function(card) {
return card.values.opt1 + card.values.opt2;
};
and then, in your template:
ng-repeat="card in cards | orderBy:myValueFunction"
Here is the working jsFiddle
The other thing worth noting is that orderBy is just one example of AngularJS filters so if you need a very specific ordering behaviour you could write your own filter (although orderBy should be enough for most uses cases).
The accepted solution only works on arrays, but not objects or associative arrays. Unfortunately, since Angular depends on the JavaScript implementation of array enumeration, the order of object properties cannot be consistently controlled. Some browsers may iterate through object properties lexicographically, but this cannot be guaranteed.
e.g. Given the following assignment:
$scope.cards = {
"card2": {
values: {
opt1: 9,
opt2: 12
}
},
"card1": {
values: {
opt1: 9,
opt2: 11
}
}
};
and the directive <ul ng-repeat="(key, card) in cards | orderBy:myValueFunction">, ng-repeat may iterate over "card1" prior to "card2", regardless of sort order.
To workaround this, we can create a custom filter to convert the object to an array, and then apply a custom sort function before returning the collection.
myApp.filter('orderByValue', function () {
// custom value function for sorting
function myValueFunction(card) {
return card.values.opt1 + card.values.opt2;
}
return function (obj) {
var array = [];
Object.keys(obj).forEach(function (key) {
// inject key into each object so we can refer to it from the template
obj[key].name = key;
array.push(obj[key]);
});
// apply a custom sorting function
array.sort(function (a, b) {
return myValueFunction(b) - myValueFunction(a);
});
return array;
};
});
We cannot iterate over (key, value) pairings in conjunction with custom filters (since the keys for arrays are numerical indexes), so the template should be updated to reference the injected key names.
<ul ng-repeat="card in cards | orderByValue">
<li>{{card.name}} {{value(card)}}</li>
</ul>
Here is a working fiddle utilizing a custom filter on an associative array: http://jsfiddle.net/av1mLpqx/1/
Reference: https://github.com/angular/angular.js/issues/1286#issuecomment-22193332
The following link explains filters in Angular extremely well. It shows how it is possible to define custom sort logic within an ng-repeat.
http://toddmotto.com/everything-about-custom-filters-in-angular-js
For sorting object with properties, this is the code I have used:
(Note that this sort is the standard JavaScript sort method and not specific to angular) Column Name is the name of the property on which sorting is to be performed.
self.myArray.sort(function(itemA, itemB) {
if (self.sortOrder === "ASC") {
return itemA[columnName] > itemB[columnName];
} else {
return itemA[columnName] < itemB[columnName];
}
});
To include the direction along with the orderBy function:
ng-repeat="card in cards | orderBy:myOrderbyFunction():defaultSortDirection"
where
defaultSortDirection = 0; // 0 = Ascending, 1 = Descending

Resources