0

我正在创建一个页面,其中有范围滑块会根据它们的设置相互影响。我有一个如下所示的监视功能,它可以以一种方式工作,但是我希望两种方式都具有监视效果,但是我不知道是否有一种方法可以在不为每个值创建一个的情况下做到这一点(大约有 12 个滑块)。

这是一个 Plunker 概述了我正在尝试做的事情: http ://plnkr.co/edit/CXMeOT?p=preview

4

3 回答 3

2

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

换句话说,问题在于数值计算是有损的,如果计算是无损的,这些问题就不会发生。

方法1(点击查看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;
});

方法二

在这个版本中,我避免了笨重的锁定机制,并将观察者与当前可用的 Angular 统一起来。我使用单个滑块的值作为规范数据源,并根据变化量重新计算总数。同样,内置工具不足以彻底表达问题,我需要手动保存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];
});

方法 3:回归基础

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

$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>

审查

这似乎是 Angular 团队尚未解决的问题。基于事件的数据绑定系统(用于淘汰赛、ember 和骨干网)将通过简单地不对有损的特定计算更改触发更改事件来解决此问题;在 Angular 的情况下,需要对摘要循环进行一些调整。我不知道我的前两个解决方案是否是 Misko 或其他角度大师的建议,但它们有效。

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

于 2013-10-11T18:47:52.147 回答
1

我相信这就是你想要的。

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

这样您就可以在同一个手表中查看所有滑块范围变量。

于 2013-10-11T18:47:34.760 回答
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);
于 2013-10-11T18:49:22.447 回答