I wonder is it possible to execute some callback only once after evaluation all (or only some) attributes of directive (without isolated scope). Attributes are really great to p
I resolved the exact same problem that I had using another approach, though I was looking for different ideas. While cmw's suggestions is working, I compared its performance against mine, and saw that the $watch method is called far too many times, so I decided to keep things the way I had implemented.
I added $observe calls for both variables I wanted to track and bound them to a debounce call. Since they both are modified with very little time difference, both $observe methods trigger the same function call, which gets executed after a short delay:
var debounceUpdate = _.debounce(function () {
setMinAndMaxValue(attrs['minFieldName'], attrs['maxFieldName']);
}, 100);
attrs.$observe('minFieldName', function () {
debounceUpdate();
});
attrs.$observe('maxFieldName', function () {
debounceUpdate();
});
You can use $watch
to evaluate a custom function rather than a specific model.
i.e.
$scope.$watch(function () {
return [$attrs.name, $attrs.surname];
}, action, true);
That will be run on all $digest
cycles, and if $watch
detects the return array (or however you want to structure your function's return value) doesn't match the old value, the callback argument to $watch
will fire. If you do use an object as the return value though, make sure to leave the true
value in for the last argument to $watch
so that $watch
will do a deep compare.
There are several ways presented to solve this problem. I liked the debounce solution a lot. However, here is my solution to this problem. This combines all the attributes in one single attribute and creates a JSON representation of the attributes that you are interested in. Now, you just need to $observe one attribute and have good perf too!
Here is a fork of the original plunkr with the implementation:
link
http://plnkr.co/edit/un3iPL2dfmSn1QJ4zWjQ
So, I've ended up with my own implementation of observeAll
method, which can wait for several changes of attributes during one call stack. It works however I'm not sure about performance.
Solution of @cmw seems to be simpler but performance can suffer for large number of parameters and multiple $digest phase runs, when object equality is evaluated many many times. However I decided to accept his answer.
Below you can find my approach:
angular.module('utils.observeAll', []).
factory('observeAll', ['$rootScope', function($rootScope) {
return function($attrs, callback) {
var o = {},
callQueued = false,
args = arguments,
observe = function(attr) {
$attrs.$observe(attr, function(value) {
o[attr] = value;
if (!callQueued) {
callQueued = true;
$rootScope.$evalAsync(function() {
var argArr = [];
for(var i = 2, max = args.length; i < max; i++) {
var attr = args[i];
argArr.push(o[attr]);
}
callback.apply(null, argArr);
callQueued = false;
});
}
});
};
for(var i = 2, max = args.length; i < max; i++) {
var attr = args[i];
if ($attrs.$attr[attr])
observe(attr);
}
};
}]);
And you can use it in your directive:
angular.module('app', ['utils.observeAll']).
directive('person', ['observeAll', function(observeAll) {
return {
restrict: 'A',
link: function($scope, $elem, $attrs) {
var action = function() {
$elem.append('name: ' + $attrs.name + '<br/> surname: ' + $attrs.surname+'<br/><br/>');
}
observeAll($attrs, action, 'name', 'surname');
}
}
}]);
Plunker here
Underscore (or lo-dash) has a once
function. If you wrap your function inside once
you can ensure your function will be called only once.
angular.module('app', []).
directive('person', function() {
return {
restrict: 'A',
link: function($scope, $elem, $attrs) {
var action = function() {
$elem.append('name: ' + $attrs.name + '<br/> surname: ' + $attrs.surname+'<br/><br/>');
}
var once = _.once(action);
$attrs.$observe('name', once);
$attrs.$observe('surname', once);
}
}
});