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


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

在示例中,我们有一个没有隔离范围的指令,它遵循两个属性: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 这里

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

name: 
surname: Brown

name: John
surname: Brown

所以问题是:可以 action 只更改名称和姓氏值的一次?


7391
2017-10-17 20:21


起源



答案:


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

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

这将在所有人身上运行 $digest 周期,如果 $watch 检测返回数组(或者你想要构造函数的返回值)与旧值不一致,回调参数为 $watch 会发射。如果你确实使用一个对象作为返回值,请确保离开 true 最后一个参数的值为 $watch 以便 $watch 会做一个深刻的比较。


11
2017-10-17 21:46



非常感谢! - iamtankist


答案:


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

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

这将在所有人身上运行 $digest 周期,如果 $watch 检测返回数组(或者你想要构造函数的返回值)与旧值不一致,回调参数为 $watch 会发射。如果你确实使用一个对象作为返回值,请确保离开 true 最后一个参数的值为 $watch 以便 $watch 会做一个深刻的比较。


11
2017-10-17 21:46



非常感谢! - iamtankist


下划线(或破折号)有一个 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);
    }
 }
});

0
2017-10-17 22:43



不幸 _.once 将仅触发回调一次,但仅更改第一个属性。我的观点是,在单个过程中的所有更改之后应该触发回调 $digest 相。 - kseb


所以,我最终得到了自己的实现 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 这里


0
2017-10-22 20:02





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

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

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

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

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

0
2017-08-13 12:35





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

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


0
2017-09-26 07:50