I'm having a massive headache right now regarding custom filters timing. I have a demo (learning Angular) gallery app in which I am using a custom filter with checkboxes to select different categories of photos.
The Simptoms: when using a custom filter on a ng-repeat directive I noticed this http://screencast.com/t/xPGX1lyTu9Yp ... after a few hours of debugging I got to the conclusion that the problem is that the data from my JSON is not there when the filter runs, though without the filter everything seems to load ok.
Here is a plnkr for this http://plnkr.co/edit/KbBg67 (I copy pasted the code, modified it a bit, not working yet, will fix it in the morning, but this is the code)
I started using deferring and promises in my services with fetches the JSON data so I the controllers and everybody else would wait for the data to load, like this [SERVICES]
angular.module('services', []).factory('getAllPosts', ['$http', '$q', '$timeout',
function($http, $q, $timeout) {
//defining the promised based API
var deffered = $q.defer();
//the getData function
var getData = function() {
//defining a empty data array
var theData = {};
//using $http to get the dat
$http.get('/wordpress/api/get_recent_posts/').success(function(data) {
// prepare data here
//assigning our data to the theData array.
// // New stuff here, we are pushing the data one by one to populate the array faster so it isn't emtpy .. ?!
theData.length = 0;
for (var i = 0; i < data.length; i++) {
theData.push(data[i]);
}
theData = data;
});
//setting a timeout for the data and waiting if necesarry.
$timeout(function() {
deffered.resolve(theData);
}, 1000);
//when it s done, return the promise... i think so. ?!
return deffered.promise;
}
return {
//creating a getData handler to use in controllers.
getData: getData
};
}])
My controller is like this [CONTROLLER]
.controller('ListController', ['$scope', 'getAllPosts', 'getCategories', '$location',
function($scope, getAllPosts, getCategories) {
$scope.name = 'list';
getAllPosts.getData().then(function(data) {
return $scope.posts = data.posts;
});
getCategories.get(function(data){
return $scope.categories = data.categories;
})
}
])
I'm using getData().then() to fetch it while it's loaded.
I realize I am not telling the same thing to the filter [FILTER]
angular.module('filters', [])
.filter('checkboxFilter', function($filter) {
return function(post, prefs) {
var i, j, k, n, out, matchingpost = [];
// loop through the post
for (i = 0; i < post.length; i++) {
console.log('I passed the length ... wtf?')
out = false;
n = 0;
if (prefs) {
// for each item, loop through the checkboxes categories
for (j = 0; j < prefs.length; j++) {
// for each category, loop through the checkboxes categories of the current category
for (k = 0; k < prefs[j].categories.length; k++) {
// test if the current item property name is the same as the filter name
if (post[i][prefs[j].slug] === prefs[j].categories[k].slug) {
// test if checkbox is checked for this property
(prefs[j].categories[k].value) ? n++ : out = true;
break;
}
}
if (out) break;
// if one filter in each categories is true, add item to the matchingpost
if (n === prefs.length) {
matchingpost.push(post[i]);
}
}
}
}
return matchingpost;
}
})
The thing is I started reading a angular book and I didn't understood many things so I went for a hands on experience, slowly every bit falls into place but this one ... I've been spending to much time on it. I think if I take it on again it will make much more sense.
Question: How could I get rid of those errors and make the filter read the data after it's been loaded?
Side-Question: Through a different service I am outputting all the existing categories in my backbone (Wordpress) and ng-repeat them in checkboxes, how would I link the checkboxes to the results of this filters? (it isn't obvious to me yet, though I have already seen some examples....)
Side-Question 2: Why do all my requests multiply, as in the first screenshot I posted, no wait, this is the part I am talking about http://screencast.com/t/lcrWnlioL3u ... I have only 44 posts so far, but it look like even after the data is there, the filter calls for it again.
This behaviour hapenned on other things too ... I am wondering what am I doing wrong.
Annotation: I am using angular 1.2.0rc1 as of tonight, all the behaviors appeared with the other versions I used: 1.0.7.0, 1.1.5.
I think the real way to do this would be to override the default $interpolateProvider
to enable filters that return promises. That way, you could defer the rendering of certain filtered expressions until they are resolved.
Remember, though, that you cannot do this for chained filters trivially. You will probably be forced to rewrite $parse
as well to enable support for chaining of promises.
I am faced with the very same problem at the moment, and as such, I might go ahead and just do it. If so, I will make sure to post a link to the answer on the github repository for my project (http://github.com/agileapes/bootstrapui).
EDIT
Another (mostly) easy way to do it is to pass an arbitrary argument to your filter that is updated via the HTTP call (or any other means):
.controller("MyController", function ($scope, $q, $timeout) {
$scope.result = null;
var deferred = $q.defer();
$timeout(function () {
$scope.result = [1, 2, 3, 4];
}, 2000);
});
here I have just updated the result via a timeout, but I don't have to do it that way. This is just for demonstration. You could update $scope.result
in any way you choose.
Here is a sample filter, that will only include even numbers in the result:
.filter('even', function () {
return function (input) {
if (!angular.isArray(input)) {
return input;
}
var result = [];
angular.forEach(input, function (x) {
if (x % 2 == 0) {
result.push(x);
}
});
return result;
}
});
Now, in my view, I can use them together, this way:
<div ng-controller="MyController">
<ul ng-if="result.length"> <!-- It is nice to not pollute the DOM with empty lists -->
<li ng-repeat="item in result | even">{{item}}</li>
</ul>
</div>
After a couple of seconds or so, the list should be populated and the ngRepeat
directive should receive the filtered result.
The trick, here, is that I have made a digest cycle happen for that particular result
variable, which means that the filter that is being fed that variable will also be re-executed, which in turns means that everything will happen smoothly and as expected.