AngularJs $观看2路绑定

时间:2013-10-11 18:30:26

标签: javascript angularjs

我正在创建一个页面,其中有一些范围滑块可以相互影响,具体取决于它们的设置。我有一个看起来像下面的手表功能,它的工作方式是单向的,但是我想两种方式都有手表效果,但我无法弄清楚是否有办法可以做到这一点而不为每个值创建一个(大约有12个滑块)。

这是一张Plunker,概述了我要做的事情: http://plnkr.co/edit/CXMeOT?p=preview

3 个答案:

答案 0 :(得分:2)

我想出了三种不同的方法来对计算值进行双向绑定。我只为assets滑块而不是roa滑块编写了解决方案,因为我assets是基本总和,roa是其他所有内容。 (@flashpunk:请解释一下,如果你需要更多关于问题这方面的帮助。)为什么这很困难的关键问题是,在javascript中,数字不是以精确的基数10格式表示的;当遇到这种不精确的损失时,角度会进入一个无限循环,根据不精确引起的变化重新计算变化。

换句话说,问题是数值计算有损,如果计算无损,则不会出现这些问题。

Approach 1 (click for plunkr)

这是我的第一个解决方案。它使用(脆弱的)锁定机制来切换规范数据源,具体取决于更改的值。为了使这个解决方案更加健壮,我将创建一个扩展Scope的角度模块,以允许更高级和复杂的观察者。如果我将来遇到问题,我可能会在github上启动一个项目;如果我这样做,我会修改这个答案。

// Both of these functions can be replaced with library functions from lodash or underscore, etc.
var sum = function(array) { return array.reduce(function(total, next) { return total + (+next); }, 0) };
var closeTo = function(a, b, threshold) { return a == b || Math.abs(a - b) <= threshold; };

$scope.sliders = [
$scope.slide1 = {
  assets: 10,
  roa: 20
}, ... ];

$scope.mainSlide = {
  assets: 0,
  roa: 0
};
// you might want to make or look for a helper function to control this "locking"
// in a better fashion, that integrates and resets itself with the digest cycle
var modifiedMainAssets = false;
var modifiedCollectionAssets = false;
$scope.$watch(function() {
      return sum($scope.sliders.map(function(slider) { return slider.assets; }))
    }, function(totalAssets) {
  if (modifiedCollectionAssets) { modifiedCollectionAssets = false; return; }
  $scope.mainSlide.assets = totalAssets;
  modifiedMainAssets = true;
});
$scope.$watch('mainSlide.assets', function(totalAssets, oldTotalAssets) {
  if (modifiedMainAssets) { modifiedMainAssets = false; return; }
  if (oldTotalAssets === null || closeTo(totalAssets, oldTotalAssets, 0.1)) { return; }

  // NOTE: has issues with floating point math. either round & accept the failures,
  // or use a library that supports a proper IEEE 854 decimal type
  var incrementAmount = (totalAssets - oldTotalAssets) / $scope.sliders.length;
  angular.forEach($scope.sliders, function(slider) { slider.assets += incrementAmount; });
  modifiedCollectionAssets = true;
});

Approach 2

在这个版本中,我避开了笨重的锁定机制,并将观察者与角度当前可用的内容统一起来。我使用单个滑块的值作为规范数据源,并根据更改的数量重新计算总数。同样,内置设施不足以彻底表达问题,我需要手动保存oldValues。 (代码可能更干净,但我没想到必须执行上述操作)。

$scope.calculateSum = function() {
  return sum($scope.sliders.map(function(slider) { return slider.assets; }));
};
$scope.mainSlide.assets = $scope.calculateSum();
var modifiedMainAssets = false;
var modifiedCollectionAssets = false;
var oldValues = [$scope.mainSlide.assets, $scope.mainSlide.assets];
$scope.$watchCollection('[calculateSum(), mainSlide.assets]'
    , function(newValues) {
  var newSum = newValues[0];
  var oldSum = oldValues[0];
  var newAssets = newValues[1];
  var oldAssets = oldValues[1];
  if (newSum !== oldSum) {
    $scope.mainSlide.assets = newSum;
  }
  else if (newAssets !== oldAssets) {
    var incrementAmount = (newAssets - oldAssets) / $scope.sliders.length;
    angular.forEach($scope.sliders, function(slider) { slider.assets += incrementAmount; });
    $scope.mainSlide.assets = $scope.calculateSum();
  }
  oldValues = [$scope.mainSlide.assets, $scope.mainSlide.assets];
});

Approach 3: Back to basics

这是我应该首先建议的。有一个规范数据源,它们是单独的滑块。对于主滑块,创建一个自定义滑块指令,该指令不会触发ng模型,而是触发报告所做更改的事件。 (您可以包装或分叉现有指令来执行此操作。)

$scope.$watch('calculateSum()', function(newSum, oldSum) {
    $scope.mainSlide.assets = newSum;
});
$scope.modifyAll = function(amount) {    
    angular.forEach($scope.sliders, function(slider) { slider.assets += amount; });
};

修改html:

Assets: <span ng-bind="mainSlide.assets"></span>
<button ng-click="modifyAll(1)">Up</button>
<button ng-click="modifyAll(-1)">Down</button>

评分

这似乎是角色团队尚未解决的问题。基于事件的数据绑定系统(在knockout,ember和backbone中使用的)将通过简单地不对有损的特定计算变化触发change事件来解决这个问题。在角度的情况下,需要对摘要周期进行一些调整。我不知道我的前两个解决方案是否是Misko或其他角色大师所建议的,但它们有效。

现在,我更喜欢方法3.如果这不满足你,请使用方法2,并清理它;但使用无损小数或分数表示,而不是内置的javascript数字类型(如for decimalthis, for fractional)。 - https://www.google.com/search?q=javascript+number+libaries

答案 1 :(得分:1)

我相信这就是你想要的。

$scope.watchList = [$scope.item1, $scope.item2];
$scope.$watchCollection('watchList', function(newValues){...});

通过这种方式,您可以在同一个手表中观看所有滑块范围变量。

答案 2 :(得分:0)

在两个方向创建$watch有什么问题?如果你有12种不同的情况,我建议建立一个小工厂功能来设置它们。您只需要确保计算值稳定,否则每个$watch语句触发另一个语句就会产生无限递归。

我猜测滑块之间的依赖关系,但我建议如下:

function bindSliders() {

    var mainSlider = arguments[0];
    var childSliders = arguments.slice(1);

    angular.forEach(childSliders, function(slider) {

        // Forward Direction
        $scope.$watch(function() {
            return slider.roa + slider.assets;
        }, function (newValue, oldValue) {
            // A child slider changed
            // Sum up all the child sliders and update mainSlider
        });

        // Reverse Direction
        $scope.$watch(function() {
            return mainSlider.assett + mainSlider.roa
        }, function (newValue, oldValue) {
            // Update this `slider` based on the updated main value

        });

    });
}

bindSliders($scope.mainSlide, $scope.slide1, $scope.slide2, $scope.slide3, $scope.slide4);