Delayed AngularJs filter initialization.

Changing filter function after async initialization.

Sometimes we need to initialize an Angular filter asynchronously. For example, information for filtering might come from a server. Here is how one can do this.

Here is the page setup

1
2
3
<body ng-app="filterExample">
<p>{{ 'a string' | aFilter }}</p>
</body>

initial attempt - does not work

My initial attempt was the most straightforward approach: just return a promise from a module.filter callback. After 1 second I would resolve the promise with the actual function.

1
2
3
4
5
6
7
8
angular.module('filterExample')
.filter('aFilter', function ($timeout) {
return $timeout(function () {
return function actualFilter(str) {
return str + ' filtered!';
};
}, 1000);
});

Unfortunately, this does not work. Angular tries to parse the expression 'a string' | aFilter and execute aFilter as a function. But this is not a function, instead it is a promise object!

filter substitution

A different approach is to use an inner worker function inside a filter. This worker function could be replaced at any moment, for example after a timeout of 1 second.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
angular.module('filterExample', [])
.filter('aFilter', function registerAFilter($timeout) {
var filterFn = function initialFilter(str) {
return str + ' filtered initially';
};
$timeout(function () {
filterFn = function newFilter(str) {
return str + ' filtered with delayed!!!';
};
}, 1000);
return function tempFilter(str) {
return filterFn(str);
};
});

You can see the filter in action at http://jsbin.com/zucunu/2

Output:

a string filtered initially
// after 1 second changes to
a string filtered with delayed!!!

Main points in the script:

  • We can inject other services into filter registration function registerAFilter. In this case I injected $timeout service.
  • The returned function tempFilter is the one called by Angular digest cycle over an over. It has access to the actual worker function filterFn via closure.
  • filterFn points at initialFilter at first, then points at newFilter.
  • $timeout service is integrated with digest cycle, thus we did not have to call $rootScope.$apply after changing the filter inner function.
  • It would make sense to cloak the page until the filters have been resolved to avoid sudden text changes.

conclusion

This is a solution to delayed filter initialization using available AngularJs services. Instead of implementing promise logic deep in the Angular expression parsing, the framework allows us to change our code inside the filter function.

We only want to initialize the filter once, and then use the result multiple times. Filtering potentially runs multiple times and any async processing per item would lead to bad performance and page flickering.