$同时观察多个属性并仅触发一次回调

时间:2013-10-17 20:21:33

标签: javascript angularjs

我想知道在评估指令的所有(或仅一些)属性(没有隔离范围)之后,只能执行一次回调。将配置传递给指令真的很棒。问题是你可以分别观察每个属性并多次触发回调。

在示例中,我们有一个没有隔离范围的指令,它遵循两个属性:name和surname。在任何更改action回调被触发后:

HTML

<button ng-click="name='John';surname='Brown'">Change all params</button>
<div person name="{{name}}" surname="{{surname}}"></div>

JS

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/>');
        }
        $attrs.$observe('name', action);
        $attrs.$observe('surname', action);
    }
 }
});

Plunker here

因此,效果是在一次点击更改名称和姓氏后,action回调被触发两次:

name: 
surname: Brown

name: John
surname: Brown

所以问题是:只有名字和姓氏值都改变了,action只能被解雇一次吗?

5 个答案:

答案 0 :(得分:12)

您可以使用$watch来评估自定义函数,而不是特定模型。

$scope.$watch(function () {
  return [$attrs.name, $attrs.surname];
}, action, true);

这将在所有$digest周期运行,并且如果$watch检测到返回数组(或者您想要构造函数的返回值)与旧值不匹配,则回调参数到$watch会发射。如果您确实使用对象作为返回值,请确保将true值保留为$watch的最后一个参数,以便$watch进行深度比较。

答案 1 :(得分:0)

下划线(或短划线)具有once功能。如果将函数包装在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);
    }
 }
});

答案 2 :(得分:0)

所以,我最终得到了自己的observeAll方法实现,它可以在一个调用堆栈中等待几个属性的更改。它有效,但我不确定性能。

@cmw的解决方案似乎更简单,但是当对象相等性被多次评估时,性能可能会受到大量参数和多个$ digest阶段运行的影响。但是我决定接受他的回答。

下面你可以找到我的方法:

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);
        }
    };
}]);

你可以在你的指令中使用它:

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

答案 3 :(得分:0)

我解决了与其他方法完全相同的问题,尽管我正在寻找不同的想法。虽然cmw的建议正在发挥作用,但我将其性能与我的相比较,并且看到$ watch方法被调用的次数太多了,所以我决定按照我实施的方式保存。

我为我想跟踪的两个变量添加了$ observe调用,并将它们绑定到去抖动调用。由于它们都以很小的时间差进行修改,因此两个$ observe方法都会触发相同的函数调用,这会在短暂的延迟后执行:

var debounceUpdate = _.debounce(function () {
    setMinAndMaxValue(attrs['minFieldName'], attrs['maxFieldName']);
}, 100);

attrs.$observe('minFieldName', function () {
    debounceUpdate();
});

attrs.$observe('maxFieldName', function () {
    debounceUpdate();
});

答案 4 :(得分:0)

有几种方法可以解决这个问题。我很喜欢去抖动解决方案。但是,这是我解决这个问题的方法。这将所有属性组合在一个属性中,并创建您感兴趣的属性的JSON表示。现在,您只需要观察一个属性并获得良好的性能!

这是原始plunkr的一个分支,具有实现: link http://plnkr.co/edit/un3iPL2dfmSn1QJ4zWjQ