在角度方面,我有一个通过服务在我的应用程序中公开的对象。
该对象上的某些字段是动态的,并且将通过使用该服务的控制器中的绑定正常更新。但是一些字段是计算属性,取决于其他字段,需要动态更新。
这是一个简单的例子(正在研究jsbin here)。我的服务模型公开了字段a
,b
和c
,其中c
是根据a + B
中的calcC()
计算的。请注意,在我的实际应用程序中,计算要复杂得多,但实质内容就在这里。
我能想到的唯一方法就是将我的服务模型绑定到$rootScope
,然后使用$rootScope.$watch
来监视任何更改a
的控制器或b
当他们这样做时,重新计算c
。但这看起来很难看。 有更好的方法吗?
第二个问题是表现。在我的完整申请中,a
和b
是大对象列表,汇总到c
。这意味着$rootScope.$watch
函数将进行大量深度数组检查,这听起来会损害性能。
我在BackBone中采用了一种平衡的方法,尽可能地减少了重新计算,但角度似乎不适合采用一种方法。 对此的任何想法都会很棒。
这是示例应用程序。
var myModule = angular.module('myModule', []);
//A service providing a model available to multiple controllers
myModule.factory('aModel', function($rootScope) {
var myModel = {
a: 10,
b: 10,
c: null
};
//compute c from a and b
calcC = function() {
myModel.c = parseInt(myModel.a, 10) * parseInt(myModel.b, 10);
};
$rootScope.myModel = myModel;
$rootScope.$watch('myModel.a', calcC);
$rootScope.$watch('myModel.b', calcC);
return myModel;
});
myModule.controller('oneCtrl', function($scope, aModel) {
$scope.aModel = aModel;
});
myModule.controller('twoCtrl', function($scope, aModel) {
$scope.anotherModel = aModel;
});
答案 0 :(得分:7)
虽然从较高的层面来看,我同意bmleite的答案($ rootScope存在使用,并且使用$ watch似乎适用于您的用例),我想提出一种替代方法。
使用$rootScope.$broadcast
将更改推送到$rootScope.$on
侦听器,然后重新计算c
值。
这可以手动完成 - 即,当您主动更改a
或b
值时,或者甚至可能在短暂超时时限制更新频率。更进一步的是在您的服务上创建一个“脏”标志,以便{}仅在需要时计算。{/ p>
显然,这种方法意味着更多地参与控制器,指令等的重新计算 - 但如果您不想将更新绑定到c
或a
的每个可能的更改,问题变成了'在哪里画线'的问题。
答案 1 :(得分:4)
我必须承认,我第一次看到你的问题并看到了你的例子我自己认为“这是错的”然而,在再次研究之后我意识到它并非如此好像我想的那样糟糕。
让我们面对事实,$rootScope
可以使用,如果你想在应用程序范围内共享任何东西,那就是放置它的最佳位置。当然你需要小心,这是所有范围之间共享的东西,所以你不想无意中改变它。但是让我们面对它,这不是真正的问题,在使用嵌套控制器时(因为子范围继承父范围属性)和非隔离范围指令,您必须要小心。 “问题”已经存在,我们不应该以此为借口不使用这种方法。
使用$watch
似乎也是一个好主意。这是框架已经免费提供给您的东西,并且完全符合您的需求。那么,为什么重新发明轮子呢?这个想法与“改变”事件方法基本相同。
在性能级别上,您的方法实际上可能“很重”,但它总是取决于您更新a
和b
属性的频率。例如,如果您将a
或b
设置为输入框的ng-model
(例如,在您的jsbin示例中),则每次用户都会重新计算c
键入一些东西......这显然是过度处理的。如果您使用软方法并在必要时仅更新a
和/或b
,那么您不应该遇到性能问题。它与使用“更改”事件或setter& getter方法重新计算c
相同。但是,如果您确实需要实时重新计算c
(即:在用户输入时),性能问题将始终存在,而不是您使用$rootScope
或$watch
这将有助于改善它。
恢复,在我看来,你的方法并不坏(根本就是!),只需要注意$rootScope
属性并避免“实时”处理。
答案 2 :(得分:3)
我意识到这是一年半之后,但由于我最近做出了相同的决定,我想我会提供一个替代答案,并且#34;为我工作&#34 ;不使用任何新值污染$rootScope
。
但它仍然依赖 $rootScope
。然而,它不是广播消息,而是简单地调用$rootScope.$digest
。
基本方法是在角度服务上提供单个复杂模型对象作为字段。您可以提供超出您认为合适的内容,只需遵循相同的基本方法,并确保每个字段都包含一个复杂的对象,其引用不会发生变化,即不要使用新的复合体重新分配字段宾语。而是仅修改此模型对象的字段。
var myModule = angular.module('myModule', []);
//A service providing a model available to multiple controllers
myModule.service('aModel', function($rootScope, $timeout) {
var myModel = {
a: 10,
b: 10,
c: null
};
//compute c from a and b
calcC = function() {
myModel.c = parseInt(myModel.a, 10) * parseInt(myModel.b, 10);
};
calcC();
this.myModel = myModel;
// simulate an asynchronous method that frequently changes the value of myModel. Note that
// not appending false to the end of the $timeout would simply call $digest on $rootScope anyway
// but we want to explicitly not do this for the example, since most asynchronous processes wouldn't
// be operating in the context of a $digest or $apply call.
var delay = 2000; // 2 second delay
var func = function() {
myModel.a = myModel.a + 10;
myModel.b = myModel.b + 5;
calcC();
$rootScope.$digest();
$timeout(func, delay, false);
};
$timeout(func, delay, false);
});
希望依赖服务模型的控制器可以自由地将模型注入其范围。例如:
$scope.theServiceModel = aModel.myModel;
直接绑定到字段:
<div>A: {{theServiceModel.a}}</div>
<div>B: {{theServiceModel.b}}</div>
<div>C: {{theServiceModel.c}}</div>
每当服务中的值更新时,一切都会自动更新。
请注意,只有将从Object继承的类型(例如数组,自定义对象)直接注入作用域时,才能执行此操作。如果您将字符串或数字等原始值直接注入范围(例如$scope.a = aModel.myModel.a
),您将获得一个放入范围的副本,因此永远不会在更新时收到新值。通常,最好的做法是将整个模型对象注入范围,就像我在我的示例中所做的那样。
答案 3 :(得分:2)
一般来说,这可能不是一个好主意。如果没有其他原因,重构变得更加困难和繁重,那么(通常)将模型实现暴露给所有调用者也是不好的做法。我们可以轻松解决这两个问题:
myModule.factory( 'aModel', function () {
var myModel = { a: 10, b: 10 };
return {
get_a: function () { return myModel.a; },
get_b: function () { return myModel.a; },
get_c: function () { return myModel.a + myModel.b; }
};
});
这是最佳实践方法。它可以很好地扩展,只在需要时调用,并且不会污染$rootScope
。
PS:当c
或a
设置为避免每次调用b
时重新计算时,您也可以更新get_c
;哪个最好取决于您的实施细节。
答案 4 :(得分:1)
从我所看到的结构来看,将a
和b
作为吸气剂可能不是一个好主意,但c
应该是一个功能......
所以我可以建议
myModule.factory( 'aModel', function () {
return {
a: 10,
b: 10,
c: function () { return this.a + this.b; }
};
});
使用这种方法,您无法将c
绑定到输入变量。但是双向绑定c
没有任何意义,因为如果设置c
的值,您如何将a
和b
之间的值分开?