Javascript 在服务中将对象绑定到 angular 的 $rootScope 是否不好?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/14573023/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-24 17:15:37  来源:igfitidea点击:

Is binding objects to angular's $rootScope in a service bad?

javascriptangularjsangularjs-rootscope

提问by latentflip

In angular, I have an object that will be exposed across my application via a service.

在 angular 中,我有一个对象将通过服务在我的应用程序中公开。

Some of the fields on that object are dynamic, and will be updated as normal by bindings in the controllers that use the service. But some of the fields are computed properties, that depend on the other fields, and need to be dynamically updated.

该对象上的某些字段是动态的,将通过使用该服务的控制器中的绑定正常更新。但是有些字段是计算属性,依赖于其他字段,需要动态更新。

Here's a simple example (which is working on jsbin here). My service model exposes fields a, band cwhere cis calculated from a + Bin calcC(). Note, in my real application the calculations are a lot more complex, but the essence is here.

这里有一个简单的例子(这是工作在jsbin这里)。我的服务模型公开字段ab并且cwherec是从a + Bin计算的calcC()。请注意,在我的实际应用中,计算要复杂得多,但本质就在这里。

The only way I can think to get this to work, is to bind my service model to the $rootScope, and then use $rootScope.$watchto watch for any of the controllers changing aor band when they do, recalculating c. But that seems ugly. Is there a better way of doing this?

我能想到得到这个工作的唯一办法,就是我的服务模式绑定到$rootScope,然后用$rootScope.$watch观看任何控制器的变化ab当他们这样做,重新计算c。但这看起来很丑陋。有没有更好的方法来做到这一点?



A second concern is performance. In my full application aand bare big lists of objects, which get aggregated down to c. This means that the $rootScope.$watchfunctions will be doing a lot of deep array checking, which sounds like it will hurt performance.

第二个问题是性能。在我的完整应用程序中ab是大对象列表,它们聚合到c. 这意味着$rootScope.$watch函数将进行大量深度数组检查,这听起来会影响性能。

I have this all working with an evented approach in BackBone, which cuts down the recalculation as much as possible, but angular doesn't seem to play well with an evented approach. Any thoughts on that would be great too.

我将这一切都与 BackBone 中的事件方法一起使用,它尽可能地减少了重新计算,但是 angular 似乎不能很好地与事件方法配合使用。对此的任何想法也会很棒。



Here's the example application.

这是示例应用程序。

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;
});

回答by Alex Osborn

Although from a high level, I agree with the answer by bmleite ($rootScope exists to be used, and using $watch appears to work for your use case), I want to propose an alternative approach.

尽管从高层次上来说,我同意 bmleite 的回答(存在 $rootScope 可供使用,并且使用 $watch 似乎适用于您的用例),但我想提出一种替代方法。

Use $rootScope.$broadcastto push changes to a $rootScope.$onlistener, which would then recalculate your cvalue.

使用$rootScope.$broadcast推变为$rootScope.$on听众,那么这将重新计算你的c价值。

This could either be done manually - i.e. when you would be actively changing aor bvalues, or possibly even on a short timeout to throttle the frequency of the updates. A step further from that would be to create a 'dirty' flag on your service, so that cis only calculated when required.

这可以手动完成 - 即当您主动更改ab值时,或者甚至可能在短时间内完成以限制更新频率。更进一步的是在您的服务上创建一个“脏”标志,以便c仅在需要时进行计算。

Obviously such an approach means a lot more involvement in recalculation in your controllers, directives etc - but if you don't want to bind an update to every possible change of aor b, the issue becomes a matter of 'where to draw the line'.

显然,这种方法意味着更多地参与控制器、指令等的重新计算 - 但是如果您不想将更新绑定到aor 的每个可能的更改b,那么问题就变成了“在哪里划线”的问题。

回答by bmleite

I must admit, the first time I read your question and saw your example I thought to myself "this is just wrong", however, after looking into it again I realized it wasn't so bad as I thought it would be.

我必须承认,当我第一次阅读您的问题并看到您的示例时,我心里想“这是错误的”,但是,在再次查看之后,我意识到情况并没有我想象的那么糟糕。

Let's face the facts, the $rootScopeis there to be used, if you want to share anything application-wide, that's the perfect place to put it. Of course you will need to careful, it's something that's being shared between all the scopes so you don't want to inadvertently change it. But let's face it, that's not the real problem, you already have to be careful when using nested controllers (because child scopes inherit parent scope properties) and non-isolated scope directives. The 'problem' is already there and we shouldn't use it as an excuse not follow this approach.

让我们面对事实,在$rootScope那里可以使用,如果您想在应用程序范围内共享任何内容,那么这是放置它的最佳位置。当然,您需要小心,这是所有范围之间共享的内容,因此您不想无意中更改它。但是让我们面对现实,这不是真正的问题,在使用嵌套控制器(因为子作用域继承父作用域属性)和非隔离作用域指令时,您已经必须小心了。“问题”已经存在,我们不应该以此为借口不遵循这种方法。

Using $watchalso seems to be a good idea. It's something that the framework already provides you for free and does exactly what you need. So, why reinventing the wheel? The idea is basically the same as an 'change' event approach.

使用$watch似乎也是一个好主意。这是框架已经免费为您提供的东西,并且正是您所需要的。那么,为什么要重新发明轮子呢?这个想法基本上与“更改”事件方法相同。

On a performance level, your approach can be in fact 'heavy', however it will always depend on the frequency you update the aand bproperties. For example, if you set aor bas the ng-modelof an input box (like on your jsbin example), cwill be re-calculated every time the user types something... that's clearly over-processing. If you use a soft approach and update aand/or bsolely when necessary, then you shouldn't have performance problems. It would be the same as re-calculate cusing 'change' events or a setter&getter approach. However, if you really need to re-calculate con real-time (i.e: while the user is typing) the performance problem will always be there and is not the fact that you are using $rootScopeor $watchthat will help improve it.

在性能级别上,您的方法实际上可能是“繁重的”,但它始终取决于您更新ab属性的频率。例如,如果您设置ab作为ng-model输入框的(就像在您的 jsbin 示例中一样),c则每次用户键入内容时都会重新计算......这显然是过度处理。如果您使用软方法和更新a和/或b仅在必要时更新,那么您不应该有性能问题。这与c使用“更改”事件或 setter&getter 方法重新计算相同。但是,如果您确实需要c实时重新计算(即:$rootScope$watch这将有助于改进它。

Resuming, in my opinion, your approach is not bad (at all!), just be careful with the $rootScopeproperties and avoid ′real-time′ processing.

恢复,在我看来,您的方法还不错(一点也不差!),只需注意$rootScope属性并避免“实时”处理。

回答by Randolpho

I realize this is a year and a half later, but since I've recently had the same decision to make, I thought I'd offer an alternative answer that "worked for me" without polluting $rootScopewith any new values.

我意识到这是一年半之后,但由于我最近做出了同样的决定,我想我会提供一个“对我有用”的替代答案,而不会污染$rootScope任何新的价值观。

It does, however still relyon $rootScope. Rather than broadcasting messages, however, it simply calls $rootScope.$digest.

但是它仍然依赖$rootScope. 然而,它不是广播消息,而是简单地调用$rootScope.$digest.

The basic approach is to provide a single complex model object as a field on your angular service. You can provide more than as you see fit, just follow the same basic approach, and make sure each field hosts a complex object whose reference doesn't change, i.e. don't re-assign the field with a new complex object. Instead, only modify the fields of this model object.

基本方法是提供一个单一的复杂模型对象作为 angular 服务上的一个字段。您可以提供比您认为合适的更多内容,只需遵循相同的基本方法,并确保每个字段承载一个复杂对象,其引用不会更改,即不要使用新的复杂对象重新分配字段。相反,只修改此模型对象的字段。

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);
});

Controllers that wish to depend on the service's model are then free to inject the model into their scope. For example:

希望依赖于服务模型的控制器可以自由地将模型注入到它们的作用域中。例如:

$scope.theServiceModel = aModel.myModel;

And bind directly to the fields:

并直接绑定到字段:

<div>A: {{theServiceModel.a}}</div>
<div>B: {{theServiceModel.b}}</div>
<div>C: {{theServiceModel.c}}</div>

And everything will automatically update whenever the values update within the service.

每当服务中的值更新时,一切都会自动更新。

Note that this will only work if you inject types that inherit from Object (e.g. array, custom objects) directly into the scope. If you inject primitive values like string or number directly into scope (e.g. $scope.a = aModel.myModel.a) you will get a copy put into scope and will thus never receive a new value on update. Typically, best practice is to just inject the whole model object into the scope, as I did in my example.

请注意,这仅在您将继承自 Object 的类型(例如数组、自定义对象)直接注入作用域时才有效。如果您将字符串或数字等原始值直接注入作用域(例如$scope.a = aModel.myModel.a),您将获得一个放入作用域的副本,因此永远不会在更新时收到新值。通常,最佳实践是将整个模型对象注入作用域,就像我在示例中所做的那样。

回答by Josh David Miller

In general, this is probably not a good idea. It's also (in general) bad practice to expose the model implementation to all of its callers, if for no other reason than refactoring becomes more difficult and onerous. We can easily solve both:

一般来说,这可能不是一个好主意。如果没有其他原因,重构变得更加困难和繁重,那么将模型实现公开给所有调用者也是(通常)不好的做法。我们可以轻松解决这两个问题:

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; }
  };
});

That's the best practice approach. It scales well, only gets called when it's needed, and doesn't pollute $rootScope.

这是最佳实践方法。它可以很好地扩展,仅在需要时才被调用,并且不会造成污染$rootScope

PS: You could also update cwhen either aor bis set to avoid the recalc in every call to get_c; which is best depends on your implementation details.

PS:您也可以cab设置为避免在每次调用时进行重新计算get_c;哪个最好取决于您的实现细节。

回答by ganaraj

From what I can see of your structure, having aand bas getters may not be a good idea but cshould be a function...

从我对你的结构的了解来看,拥有ab作为吸气剂可能不是一个好主意,但c应该是一个函数......

So I could suggest

所以我可以建议

myModule.factory( 'aModel', function () {
  return {
    a: 10,
    b: 10,
    c: function () { return this.a + this.b; }
  };
});

With this approach you cannot ofcourse 2 way bind cto an input variable.. But two way binding cdoes not make any sense either because if you set the value of c, how would you split up the value between aand b?

使用这种方法,您当然不能以 2 路绑定c到输入变量。但是,双向绑定c也没有任何意义,因为如果您设置了 的值c,您将如何拆分a和之间的值b