Problem
I have a $scope.users
that I use to show a table and I need to apply a filter with multiple options,
Here is the $scope.users
object:
[
{
"login": "andres.un1@mail.com",
"merchant": {
"merchantId": 500095,
"status": "ENABLED",
"defaultAccountId": null,
"accounts": {
"500092": "ADMIN",
"500178": "CONSULT",
"500180": "ADMIN",
"500182": "ADMIN",
"500201": "ADMIN"
}
},
"name": "Carlos"
},
{
"login": "andres.un2@mail.com",
"merchant": {
"merchantId": 500095,
"status": "DISABLED",
"defaultAccountId": null,
"accounts": {
"500092": "CONSULT",
"500178": "MANAGER",
"500180": "ADMIN",
"500182": "ADMIN",
"500201": "ADMIN"
}
},
"name": "Carlos Andres"
},
{
"login": "cosme.fulanito@mail.com",
"merchant": {
"merchantId": 500095,
"status": "DISABLED",
"defaultAccountId": null,
"accounts": {
"500092": "CONSULT",
"500178": "MANAGER",
"500180": "ADMIN",
"500182": "ADMIN",
"500201": "ADMIN"
}
},
"name": "Cosme"
}
]
In the filter I pass other object from the $scope
to filter the data, in this case is $scope.searchParams
that I previously calculate in a directive.
$scope.searchParams
:
{
"name": [
"arl"
],
"login": [
"@"
],
"merchant.status": [
"ENABLED"
],
"merchant.accounts": [
"501678",
"501675",
"MANAGER",
"ADMIN"
]
}
As you can see, name
and login
are unique in the filter, but status
and account
can have multiple values to apply in the filter. Here is my approach to do that but I think is very ineffective, it is important that each filter is applied on the data previously filtered by another filter:
var usrUser = function () {
var SEPARATOR = '.';
return function (users, filters) {
// when the filter is not defined shows all results
if(filters === undefined || angular.equals({}, filters)){
return users;
}
var matchName = function (element){
if(element['name'].indexOf(filters['name']) >= 0){
return element;
}
};
var matchLogin = function (element){
if(element['login'].indexOf(filters['login']) >= 0){
return element;
}
};
var matchState = function (element){
if(filters['merchant.status'].indexOf(element['merchant']['status']) >= 0){
return element;
}
};
var matchAccount = function (element){
var match = false;
angular.forEach(element['merchant']['accounts'], function(profile, accountId){
if(filters['merchant.accounts'].indexOf(accountId) >= 0 ||
filters['merchant.accounts'].indexOf(profile) >= 0){
match = true;
return;
}
});
if(match){
return element;
}
};
if(filters['name'] !== undefined){
users = users.filter(matchName);
}
if(filters['login'] !== undefined){
users = users.filter(matchLogin);
}
if(filters['merchant.status'] !== undefined){
users = users.filter(matchState);
}
if(filters['merchant.accounts'] !== undefined){
users = users.filter(matchAccount);
}
return users;
};
};
//-- register filter
angular.module('users').filter('usrUser', usrUser);
Finally here is how I am using the fiter: <tbody ng-repeat="user in users | secUser:searchParams " class="table-striped">
Is there a better way to implement this filter considering that in the future there can be more filter fields? Also I think is very coupled with the view.
Solution
In general, your data is structured very poorly for your filtering use cases. Having to iterate an entire array of users every time you want to apply a filter (and then having some of those filter methods further iterate nested data structures) will become a very slow process once you have a large number of data objects that you want to filter (not to mention also apply multiple filters towards).
It is also unclear how your filtering logic support AND vs. OR across the filtering criteria, so not sure how flexible/extensible what you have really is.
Finally, the approach you take on chaining the filters might need to be something you think about optimizing at some point. If you have fields that are unique, like login presumably is, these would almost seem to need different filter treatment than trying to iterate through all users making this comparison. These sorts of filters should be binary in the sense that the match will either return exactly one item or no items if the match isn’t fulfilled. So, this is not so much a filter as it is an indexed lookup. This ties back to my concern about your data structure. If you have unique fields, you should be able to perform an O(1) lookup to determine if that object with that unique value exists, not have to perform an O(n) iteration across all objects to look for this unique value.