我正在创建一个页面,其中有一些范围滑块可以相互影响,具体取决于它们的设置。我有一个看起来像下面的手表功能,它的工作方式是单向的,但是我想两种方式都有手表效果,但我无法弄清楚是否有办法可以做到这一点而不为每个值创建一个(大约有12个滑块)。
这是一张Plunker,概述了我要做的事情: http://plnkr.co/edit/CXMeOT?p=preview
答案 0 :(得分:2)
我想出了三种不同的方法来对计算值进行双向绑定。我只为assets
滑块而不是roa
滑块编写了解决方案,因为我assets
是基本总和,roa
是其他所有内容。 (@flashpunk:请解释一下,如果你需要更多关于问题这方面的帮助。)为什么这很困难的关键问题是,在javascript中,数字不是以精确的基数10格式表示的;当遇到这种不精确的损失时,角度会进入一个无限循环,根据不精确引起的变化重新计算变化。
换句话说,问题是数值计算有损,如果计算无损,则不会出现这些问题。
这是我的第一个解决方案。它使用(脆弱的)锁定机制来切换规范数据源,具体取决于更改的值。为了使这个解决方案更加健壮,我将创建一个扩展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;
});
在这个版本中,我避开了笨重的锁定机制,并将观察者与角度当前可用的内容统一起来。我使用单个滑块的值作为规范数据源,并根据更改的数量重新计算总数。同样,内置设施不足以彻底表达问题,我需要手动保存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];
});
这是我应该首先建议的。有一个规范数据源,它们是单独的滑块。对于主滑块,创建一个自定义滑块指令,该指令不会触发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 decimal或this, 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);