AngularJs FilterBy property a in array A, where a exists in array B - angularjs

I am using angular-filter and I want to loop the collection A and only display items where A.a exists in array B.
A = [{a:'mouse', c:'Tom'}, {a:'cat', c:'Jerry'}];
B = ['cat', 'dog'];
Then, I want something like:
<div ng-repeat="item in A | filterBy: ['item.a'] : B">
{{item.c}}
</div>
Would return: <div>Jerry</div> because cat is in B. I imagine that I can achieve this with pick, but was wondering if I can do this with filterBy?

if using lodash will do for you and you are not married to the filter idea. here is a quick solution.
define on the scope a function that gets the selected ones and bind that to the ng-repeat:
$scope.commonItems = function(){
return _.filter(A, function(itemA){ return _.includes(B, itemA.a) })
}
and then:
<div ng-repeat="item in commonItems()">
{{item.c}}
</div>

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 filter an array

I have the following ng-repeat:
<td data-ng-repeat="scheduleAbsenceDayContainer in scheduleAbsenceMonthContainer.scheduleAbsenceDayContainers track by $index
and scheduleAbsenceDayContainer can contain an array of scheduleIntervalContainers:
My question now would be if there is any possibility to filter all scheduleIntervalContainers with a special containerType value.
My filter- name is is calles vm.absenceType:
<select name="typeSelection" id="typeSelection" data-ng-model="vm.absenceType"
I have tried this one withour success, because I don't know how to gete any or when not possible only the first one:
<td data-ng-repeat="scheduleAbsenceDayContainer in scheduleAbsenceMonthContainer.scheduleAbsenceDayContainers track by $index | filter: {scheduleIntervalContainers(any or - when not possible than only first): {containerType.value: vm.absenceType}}"
You will need to use nested ng-repeat. Then you can use a custom filter to filter the scheduleIntervalContainers using your vm.absenceType model variable.
<div data-ng-repeat="scheduleAbsenceDayContainer in scheduleAbsenceMonthContainer.scheduleAbsenceDayContainers track by $index">
<div data-ng-repeat="scheduleIntervalContainer in scheduleAbsenceDayContainer.scheduleIntervalContainers | filter: check" >
</div>
</div>
Custom filter function
$scope.check = function(a){
if (angular.equals(a.containerType,$scope.vm.absenceType))
return true;
else
return false;
}
You could also implement this without a custom filter function by renaming your filter model (if it's possible) to vm.containerType and giving filter : vm in the nested ng-repeat.

Add an extra Capital letter element only at the start of each new letter in ng-repeat

I have an array sorted like so:
[aa,ab,ba,bb]
I want to render it like this:
A
aa
ab
B
ba
bb
how can I modify this forech to do this?
<div ng-repeat="item in array" >
{{item}} <br>
</div>
You can do it like this :
$scope.capitalize = function(array){
//group all elements by first letter using lodash
var items = _.groupBy(array,function(element){return element.charAt(0)});
//flatten object arrays to one array
return _.flatten(items,function(elements,letter){return [letter.toUpperCase(),elements] });
}
and on html you have
<div ng-repeat="item in capitalize(array)">
{{item}} <br/>
</div>
Working fiddle :
http://jsfiddle.net/qmw0qp6p/4/
I added also #sameer resolution, but it is not what you want.
I think you don't want to print the words and their first letters like you did in the question, but more like this:
A
- aa
- ab
B
- ba
- bb
To do so, I would aggregate the strings by their first letter using a custom groupBy filter (reusable), and then simply loop on the obtained object.
See this JSFiddle.
The cache system I implemented allows fast access to already computed results and avoids infinite digest loop (occurring because a new object is each time returned by Array.prototype.reduce).
Please try the below code
angular.module('module', []).
filter('capitalize', function() {
return function(input, all) {
return (!!input) ? input.replace(/([^\W_]+[^\s-]*) */g, function(txt){return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();}) : '';
}
});
and In HTML
<div ng-repeat="item in array" >
{{item | capitalize}} <br>
</div>

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