Stateful filters in Angular 1.3
Angular 1.3 comes with a lot of cool features and improvements. We already covered a couple of them. You can for example read about one-time bindings, ngModelOptions or the newly introduced Angular-hint module that helps you out writing better Angular code.
However, it turns out that, even if the 1.3 release looks like a feature release, it comes with a change that might break your existing code. It handles all filters stateless by default and in this article we’re going to take a look at what this means and how we can deal with that.
The filter behaviour you know
I think I don’t have to go into much detail when it comes to how to use filters in general. We apply them with a |
symbol in our interpolation expressions and are able to pass additional parameters by chaining them with a :
symbol.
For instance, in the following example, we use the json
filter to convert a JavaScript object into a JSON string:
{{ jsonExpression | json }}
Expecting jsonExpression
to look something like: {'name':'value'}
, the filter would return a string that looks something like this:
{
"name": "value"
}
If we use a filter that can be configured with additional parameters, we can pass them to the filter by chaining them with a :
symbol right in the expression. The following example uses the currency
filter to format the given expression accordingly and uses an optional symbol
parameter to change the default currency symbol for this particular use case.
{{ amount | currency:'€' }}
Okay, so this is pretty straight forward right? A filter takes an expression and uses it as input to manipulate the expressions value and returns (ideally) a string, so it can be used in our HTML right away.
We can go even further and build a custom filter that depends on another service to manipulate the given input. Let’s assume we have a filter like this:
angular.module('myApp', [])
.filter('customFilter', ['someService', function (someService) {
return function customFilter(input) {
// manipulate input with someService
input += someService.getData();
return input;
};
}]);
In this example we have a filter that depends on someService
and this particular service apparently comes with a method getData()
, which is used to change the value of input
that gets returned.
Again, this is pretty straight forward. Filters in Angular follow the same rules as other component types like services, factories etc, when it comes to dependency injection. So basically it’s totally fine and valid to have dependencies in filter components. However, filters that have dependencies are usually stateful. And this is where the code might break.
But what does that mean? And why is it a problem at all? Well, let’s take a look at what changed in Angular 1.3, so we get a better idea of what causes problems.
Filters in 1.3
In order to make Angular faster, a lot of changes landed in the 1.3 release that come with performance improvements. One of them is how filters behave by default. We talked about what filters do and how we can use them, but we didn’t talk about the fact, that they always came with a relatively big drawback. Each filter creates a new watcher. Having to many watchers registered can slow down our app, since the more watchers are registered, the more work has to be done during the $digest
cycle. That’s why we usually should avoid using too many filters.
However, there’s a reason why Igor Minar said that:
“Angular 1.3 is the best Angular yet!”
In version 1.3, filters are much smarter. By default, they cache the evaluated value so they don’t have to be re-evaluated all the time. Getting back to our simple {% raw %}{{ jsonExpression | json}}{% endraw %}
example, the expression only gets re-evaluated when jsonExpression
changes, which makes our code execution much faster.
To make it work like this, Angular assumes that, as long as the passed expression doesn’t change, the result of the expression doesn’t change either. It’s stateless. And this is where our code might break. Think about what that means in cases where your filter depends on other services, like our customFilter
.
But to get a better picture, let’s take a look at the translate
filter that comes with the angular-translate module. It consumes translation ids to look them up in a registered translation table, using the $translate
service and returns the dedicated translation. It is stateful.
Here’s what it looks like:
{{ 'TRANSLATIONID' | translate }}
As we already discussed, Angular caches the value of this expression and won’t re-evaluate it unless 'TRANSLATIONID'
changes. This is actually a problem, because 'TRANSLATIONID'
never changes. When the user changes the language, the given translation id again, is looked up by the filter in a translation table, but the expression stays the exact same.
So how do we tell Angular, that expressions that are stateful have to be re-evaluated? It’s easy. All we have to do is to add a $stateful
property to our filter that flags it as stateful. Here we see our customFilter
being flagged accordingly:
angular.module('myApp', [])
.filter('customFilter', ['someService', function (someService) {
function customFilter(input) {
// manipulate input with someService
input += someService.getData();
return input;
}
customFilter.$stateful = true;
return customFilter;
}]);
That’s it. Setting the $stateful
property to true
does the trick (angular-translate’s filter comes with that flag already). Keep in mind that it’s in general recommended to avoid building stateful filters, because the execution of those can’t be optimized by Angular. Better build stateless filters that get all needed information as parameters.
To sum it up, make sure to flag your stateful filters as stateful in order to make them work with Angular 1.3. Hopefully this article made clear why these changes are a requirement.