I've read an article on some AngularJS pitfalls using scopes, and it states that you should not use a function in expressions, and I understand that an expression may be evaluated every time the framework think it's needed (and that can happen a lot) and it would be inefficient to call an expensive function over and over. But, what if that function only does some computation based on values already loaded into the scope? For instance, suppose I have a scope with 3 different properties, and some state is determined by the combination of those properties' values. I can calculate that state in a function:
$scope.state = function() {
return prop1 && prop2 && prop3;
};
and call that function from an expression. The alternative would be to call the function every time each of the properties is changed so that the state value is cached:
$scope.prop1 = someValue;
$scope.state = getState();
...
$scope.prop2 = someOtherValue;
$scope.state = getState();
...
$scope.prop3 = yetAnotherValue;
$scope.state = getState();
Is it really that bad to call a function directly from an expression in such a case? If so, is the only alternative to cache the computed value, potentially in many different places or is there some another approach I'm missing?
No, it's not that bad.
Not using functions in expressions would result in HTML bloated with inline javascript.
Consider the elegance of this code:
<span ng-show="(form.firstName.$dirty || submitted) && form.firstName.$error.required">First name is required!</span>
...
<span ng-show="(form.lastName.$dirty || submitted) && form.lastName.$error.required">Last name is required!</span>
...
<span ng-show="(form.email.$dirty || submitted) && form.email.$error.required">Email is required!</span>
versus this:
<span ng-show="isInvalid('firstName')">First name is required!</span>
...
<span ng-show="isInvalid('lastName')">Last name is required!</span>
...
<span ng-show="isInvalid('email')">Email is required!</span>
function Ctrl($scope){
$scope.isInvalid = function(field){
return ($scope.form[field].$dirty || $scope.submitted) && $scope.form[field].$error.required;
};
$scope.submit = function(){
$scope.submitted = true;
// $http.post ...
}
}
Even Angular authors encourage using functions in expressions.
Functions in expressions are welcome in Angular, but with these forethoughts:
- Functions should be "light" (in computational terms).
- Functions should return the same output given the same input.
- Functions should be self-contained (they should not affect other scope members) because otherwise they could create a $digest loop.
- Functions should be cacheable (if possible).
One option is to set a $watch
on the state condition. $watch
can take a function parameter and so you can do this:
$scope.$watch(function(){
return $scope.prop1 && $scope.prop2 && $scope.prop3;
},function(val){
$scope.state = val;
});
Here is a minimal demo