FIlter in controller with multiple values - angularjs

I have 3 switches (checkboxes, they return true or false) to filter in a list.
The list:
vm.products = Product.query();
In my controller, i want to filter vm.products, everytime one of the switchboxes/checkboxes get changed.
All i got so far, is a none working, filter argument:
vm.products = $filter('filter')('id', 1);
The parameter 'filter' - sems like its pointing at a directive? Do i have to do that? And what would be the best way of making a dynamic filter function/builder, when there is multiple values to check on?

I found a way using a custom filter;
.filter("myFilter", function(){
return function(products, productTypes){
var selectedProducts = [];
for (i = 0; i < products.length; i++) {
if (productTypes.indexOf(products[i].productTypeId.toString()) > -1) {
selectedProducts.push(incidents[i]);
}
}
return selectedProducts;
};
})
I then use this in my controller to call the filter function;
vm.filterTypeUpdated = function ($event, typeId) {
var productTypesId = "";
if (vm.filterType1)
productTypesId += "1,";
if (vm.filterType2)
productTypesId += "2,";
if (vm.filterType3)
productTypesId += "3,";
vm.productsRoot = $filter('myFilter')(vm.products, productTypesId);
}
I don't feel like this is the cleanest way of doing it, but it works. If there are any inputs on optimizing this, i am all ears :-)

Related

Infinite Digest Loop in AngularJS filter

I have written this custom filter for AngularJS, but when it runs, I get the infinite digest loop error. Why does this occur and how can I correct this?
angular.module("app", []).
filter('department', function(filterFilter) {
return function(items, args) {
var productMatches;
var output = [];
var count = 0;
if (args.selectedDepartment.Id !== undefined && args.option) {
for (let i = 0; i < items.length; i++) {
productMatches = items[i].products.filter(function(el) {
return el.Order__r.Department__r.Id === args.selectedDepartment.Id;
});
if (productMatches.length !== 0) {
output[count] = {};
output[count].products = productMatches;
output[count].firstProduct = items[i].firstProduct;
count++;
}
}
}
return output;
};
}).
This is the relevant HTML:
<tr class='destination' ng-repeat-start='pickupAccount in pickupAccounts | department : {"selectedDepartment": selectedDepartment, "option": displayExclusive }'>
<!-- td here -->
</tr>
displayExclusive is boolean.
I have written this custom filter for AngularJS, but when it runs, I get the infinite digest loop error.
Keep in mind that filter should return array of the same object structure. When we activate filter, it fires digest cycle that will run over our filter again. If something changed in output list - fires new digest cycle and so on. after 10 attempts it will throw us Infinite Digest Loop Exception
Testing
This empty filter will works (100%). Actually we do nothing here but return the same object that filter receives.
filter('department', function(filterFilter) {
return function(items, args) {
var output = items;
return output;
};
})
Now the main idea is: write some condition to push to output objects from input list a.e. items based on some if statement, a.e.
var output = [];
if (args.selectedDepartment.Id !== undefined && args.option) {
angular.forEach(items, function(item) {
if(<SOME CONDITION>) {
output.push(item);
}
});
}
By this way it will work too.
our case:
we have this logic:
productMatches = items[i].products.filter(function(el) {
return el.Order__r.Department__r.Id === args.selectedDepartment.Id;
});
if (productMatches.length !== 0) {
output[count] = {};
output[count].products = productMatches;
output[count].firstProduct = items[i].firstProduct;
count++;
}
Here we completely modified object that has been stored in output.
So next digest cycle our items will change again and again.
Conclusion
The main purpose of filter is to filter list and not modify list object content.
Above mentioned logic you wrote is related to data manipulation and not filter. The department filter returns the same length of items.
To achieve your goal, you can use lodash map or underscorejs map for example.
This happens when you manipulate the returned array in a way that it does not match the original array. See for example:
.filter("department", function() {
return function(items, args) {
var output = [];
for (var i = 0; i < items.length; i++) {
output[i] = {};
output[i] = items[i]; // if you don't do this, the next filter will fail
output[i].product = items[i];
}
return output;
}
}
You can see it happening in the following simplified jsfiddle: https://jsfiddle.net/u873kevp/1/
If the returned array does have the same 'structure' as the input array, it will cause these errors.
It should work in your case by just assigning the original item to the returned item:
if (productMatches.length !== 0) {
output[count] = items[i]; // do this
output[count].products = productMatches;
output[count].firstProduct = items[i].firstProduct;
count++;
}
output[count] = {};
Above line is the main problem. You create a new instance, and ng-repeat will detect that the model is constantly changed indefinitely. (while you think that nothing is changed from the UI perspective)
To avoid the issue, basically you need to ensure that each element in the model remains the 'same', i.e.
firstCallOutput[0] == secondCallOutput[0]
&& firstCallOutput[1] == secondCallOutput[1]
&& firstCallOutput[2] == secondCallOutput[2]
...
This equality should be maintained as long as you don't change the model, thus ng-repeat will not 'wrongly' think that the model has been changed.
Please note that two new instances is not equal, i.e. {} != {}

AngularJS mysql filter multiple element

Hi this is my hotel project. But I'm having a problem with a filter.
I want to filter the data in the amenities column.
This is: my fiddle
It works if you select a single checkbox but it does not work if you select multiple checkboxes.
I suspect the problem arises from indexof instead of what I can use. What method should I follow?
How to change this line: indexOf(x);
This is my bad code:
//PROBLEM FILTER HERE
$scope.am_en = function()
{
//get input value
x = $(".hosting_amenities input:checkbox:checked").map(function(){return $(this).val();}).get();
//filter
$scope.ot_Filter = function (location) {
return location.amenities.indexOf(x) !== -1;
};
}
The problem is indeed caused by the function $scope.am_en, in the declaration of the inner function $scope.ot_Filter
When you select multiple checkboxes, your x variable is an array of objects, so you should do a loop and can create a variable to check whether the element should be shown or not. You can do it as follows:
$scope.ot_Filter = function (location) {
var shouldBeShown = false;
for (var i = 0; i < x.length; i++) {
if (location.amenities.indexOf(x[i]) !== -1) {
shouldBeShown = true;
break;
}
}
return shouldBeShown;
};
I modified your jsfiddle, so that you can see this solution working properly.

Angular sequential $http requests of multiple elements in list?

Say I have a list:
var mylist = ["a","b","c","d"];
I want to request data for each one of these like so and get the responses back in the same order.
var comeback = [];
getMyData()
function getMyData() {
for (int i = 0; i < mylist.length; i++) {
$http.get("http://myurl/" + mylist[i]).success(function(data) {
results.append(data);
});
}
}
How can I make sure that the "comeback" list has all the responses based on "a", "b", etc. in the same order? What is the best way to write this?
Chaining the promises will make them execute in series. Something like:
var results = [];
getMyData(0);
function getMyData(i) {
return $http.get("http://myurl/" + mylist[i]).success(function(data) {
results.push(data);
i++;
if(i < mylist.length) {
getMyData(i);
}
});
}
Note: If you want to do more advanced validation and error checking, you would need to use $q.

How do I filter by 'keywords' with angular.js?

I'm trying to create a robust filtering system with basic HTML & angular.js, so far with the help of a few kind people on here, I've ended up with this:
http://codepen.io/liamtarpey/pen/jfvDK
It works fine, the only issue is if you type something backwards, for example if the user searches for: 'taco mexican' or even 'mexican taco', you'd get no results at all.
Is there a way to use the filter as more of a keyword filter rather than a string filter?
You will have to iterate through every property for every word in the search term. This works:
$scope.search = function(user) {
var match = true;
$scope.query.split(" ").forEach(function(word) {
if (match === true) {
var wordMatch = false;
for (var prop in user) {
if (user.hasOwnProperty(prop)) {
if(user[prop].toLowerCase().indexOf(word.toLowerCase()) > -1) wordMatch = true;
}
}
}
match = wordMatch;
});
return match
}

Testing Angular Filter That Returns An Array with Jasmine

So, I'm having issues testing an angular filter that takes an array that has previously been sorted by a group property. It uses a flag property to indicate that the item is the first observation of that group, and then false for subsequent observations.
I'm doing this to have a category header in the UI with an ng-repeat directive.
When I test the filter, the output does not return the array with the flags unless I create new objects for the return array. This is a problem, because it causes an infinite loop when running in a webpage. The code works in the webpage when it just adds a flag property to the input object.
Is there some additional step I should be taking to simulate how angular handles filters so that it outputs the proper array?
This is what my test looks like right now.
describe('IsDifferentGroup', function() {
var list, itemOne, itemTwo, itemThree;
beforeEach(module("App.Filters"));
beforeEach(function () {
list = [];
itemOne = new ListItem();
itemTwo = new ListItem();
itemThree = new ListItem();
itemOne.group = "A";
itemTwo.group = "B";
itemThree.group = "C";
list.push(itemOne);
list.push(itemOne);
list.push(itemOne);
list.push(itemOne);
list.push(itemTwo);
list.push(itemThree);
list.push(itemThree);
list.push(itemThree);
list.push(itemThree);
list.push(itemThree);
});
it('should flag the items true that appear first on the list.', (inject(function (isDifferentGroupFilter) {
expect(list.length).toBe(10);
var result = isDifferentGroupFilter(list);
expect(result[0].isDifferentGroup).toBeTruthy();
expect(result[1].isDifferentGroup).toBeFalsy();
expect(result[4].isDifferentGroup).toBeTruthy();
expect(result[5].isDifferentGroup).toBeTruthy();
expect(result[6].isDifferentGroup).toBeFalsy();
expect(result[9].isDifferentGroup).toBeFalsy();
})));
});
And here is something like the code with the filter:
var IsDifferentGroup = (function () {
function IsDifferentGroup() {
return (function (list) {
var arrayToReturn = [];
var lastGroup = null;
for (var i = 0; i < list.length; i++) {
if (list[i].group != lastGroup) {
list[i].isDifferentGroup = true;
lastAisle = list[i].group;
} else {
list[i].isDifferentGroup = false;
}
arrayToReturn.push(list[i]);
}
return arrayToReturn;
});
}
return IsDifferentGroup;
})();
Thanks!
I figured out my issue.
When I was passing the items into the list, I just pushed a pointer to an item multiple times. I was not passing in unique objects so the flag was being overridden by the following flag in the array(I think). So, I just newed up 10 unique objects using a loop, pushed them into the array and ran it through the filter. And it worked.
I'm not entirely sure my analysis is correct about the override, because itemTwo was not being flagged as unique when it was the only itemTwo in the array. But the test is working as I would expect now so I'm going to stop investigating the issue.

Resources