設定に応じて互いに影響する範囲スライダーがあるページを作成しています。私は以下のような時計機能を持っていますが、それは一方向に機能しますが、両方の方法で時計効果を持ちたいのですが、値ごとに1つ作成せずにこれを行う方法があるかどうかわかりません(約12個のスライダーがあります)。
これが私がやろうとしていることの概要を説明するプランカーです: http://plnkr.co/edit/CXMeOT?p=preview
設定に応じて互いに影響する範囲スライダーがあるページを作成しています。私は以下のような時計機能を持っていますが、それは一方向に機能しますが、両方の方法で時計効果を持ちたいのですが、値ごとに1つ作成せずにこれを行う方法があるかどうかわかりません(約12個のスライダーがあります)。
これが私がやろうとしていることの概要を説明するプランカーです: http://plnkr.co/edit/CXMeOT?p=preview
この計算値の双方向バインディングを行う 3 つの異なる方法を考え出しました。私は基本的な合計であり、すべて一緒に何か他のものであるため、assets
スライダーではなくスライダーのソリューションのみを書きました。(@flashpunk:質問のその側面についてさらに支援が必要な場合は、説明してください。)これが難しい理由の主な問題は、JavaScriptでは、数値が正確な10進形式で表されないことです。この不正確さが失われると、angular は無限ループに入り、不正確さによって引き起こされた変更に基づいて変更を再計算します。roa
assets
roa
つまり、問題は数値計算が非可逆であるということです。これらの問題は、計算が無損失であれば発生しません。
これが私の最初の解決策でした。(壊れやすい) ロック メカニズムを使用して、変更された値に応じて標準データ ソースを切り替えます。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];
});
これは、おそらく最初にアドバイスすべきだったものです。個々のスライダーである 1 つの正規データ ソースを用意します。メイン スライダーについては、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 の場合、ダイジェスト サイクルを微調整する必要があります。私の最初の 2 つの解決策が、Misko や別の角度の専門家によってアドバイスされるものかどうかはわかりませんが、うまくいきます。
今のところ、私はアプローチ 3 を好みます。それで満足できない場合は、アプローチ 2 を使用してクリーンアップします。ただし、組み込みの JavaScript Number 型ではなく、無損失の 10 進数または分数表現を使用します ( decimal の場合は this 、fractional の場合は this のように)。-- https://www.google.com/search?q=javascript+number+libaries
私はこれがあなたが望むものだと信じています。
$scope.watchList = [$scope.item1, $scope.item2];
$scope.$watchCollection('watchList', function(newValues){...});
そうすれば、同じウォッチですべてのスライダー スコープ変数を監視できます。
$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);