我有服务,说:
factory('aService', ['$rootScope', '$resource', function ($rootScope, $resource) {
var service = {
foo: []
};
return service;
}]);
我想使用 foo
来控制以HTML呈现的列表:
<div ng-controller="FooCtrl">
<div ng-repeat="item in foo">{{ item }}</div>
</div>
为了让控制器检测到 aService.foo
何时被更新,我将这个模式拼凑在一起,我将aService添加到控制器的 $scope
然后使用 $scope.$watch()
:
function FooCtrl($scope, aService) {
$scope.aService = aService;
$scope.foo = aService.foo;
$scope.$watch('aService.foo', function (newVal, oldVal, scope) {
if(newVal) {
scope.foo = newVal;
}
});
}
这感觉很长,我一直在每个使用服务变量的控制器中重复它 . 有没有更好的方法来完成观察共享变量?
21 回答
如果你想避免
$watch
的暴政和开销,你总是可以使用好的旧观察者模式 .在服务中:
在控制器中:
在这样的场景中,多个/未知对象可能对更改感兴趣,请从正在更改的项目中使用
$rootScope.$broadcast
.而不是创建自己的监听器注册表(必须在各种$ destroys上清理),您应该能够从相关服务
$broadcast
.您仍然必须在每个侦听器中对
$on
处理程序进行编码,但该模式与多次调用$digest
分离,因此避免了长时间运行的观察者的风险 .这样,听众也可以来自DOM和/或不同的子范围,而服务不会改变其行为 .
更新:示例
广播在“全球”服务中最有意义,可能会影响您应用中的无数其他内容 . 一个很好的例子是用户服务,其中可能发生许多事件,例如登录,注销,更新,空闲等 . 我相信这是广播最有意义的地方,因为任何范围都可以监听事件,没有甚至注入服务,它不需要评估任何表达式或缓存结果来检查更改 . 它只是激发和忘记(所以确保它是一个发射后的通知,而不是需要采取行动的事情)
当save()函数完成且数据有效时,上面的服务将向每个范围广播一条消息 . 或者,如果它是$ resource或ajax提交,请将广播调用移动到回调中,以便在服务器响应时触发 . 广播特别适合这种模式,因为每个听众只需等待事件而无需检查每个$ digest上的范围 . 听众看起来像:
其中一个好处是消除了多个 Watch . 如果您正在组合字段或派生上述示例中的值,则必须同时查看firstname和lastname属性 . 只有在更新时替换了用户对象时,才能看到getUser()函数,如果用户对象只更新了其属性,则不会触发它 . 在这种情况下,你必须做一个深度观察,这是更密集的 .
$ broadcast将消息从它调用的范围发送到任何子范围 . 所以从$ rootScope调用它将触发每个范围 . 例如,如果您从控制器的范围进行$广播,则只会在从控制器范围继承的范围内触发 . $ emit方向相反,行为类似于DOM事件,因为它会使范围链冒泡 .
请记住,在某些情况下,$ broadcast非常有意义,并且有些情况下$ watch是更好的选择 - 特别是在具有非常特定的监视表达的隔离范围中 .
我使用与@dtheodot类似的方法,但使用角度承诺而不是传递回调
然后只需使用
myService.setFoo(foo)
方法在服务上更新foo
. 在您的控制器中,您可以将其用作:then
的前两个参数是成功和错误回调,第三个是通知回调 .Reference for $q.
没有 Watch 或观察者回调(http://jsfiddle.net/zymotik/853wvv7s/):
JavaScript的:
HTML
这个JavaScript的工作原理是我们从服务传递一个对象而不是一个值 . 从服务返回JavaScript对象时,Angular会将监视添加到其所有属性中 .
还要注意我正在使用'var self = this',因为我需要在$ timeout执行时保持对原始对象的引用,否则'this'将引用window对象 .
据我所知,你不必做那样详细的事情 . 您已经将foo从服务分配给范围,因为foo是一个数组(反过来它是一个通过引用分配的对象!) . 所以,你需要做的就是这样:
如果有的话,同一个Ctrl中的其他变量依赖于foo更改然后是,你需要一个 Watch 来观察foo并对该变量进行更改 . 但只要它是一个简单的参考,观看是不必要的 . 希望这可以帮助 .
我偶然发现了这个寻找类似问题的问题,但我认为它应该对正在发生的事情以及一些其他解决方案进行彻底的解释 .
当一个角度表达式(例如您使用的角色表达式)存在于HTML中,Angular会自动为
$scope.foo
设置$watch
,并且每当$scope.foo
更改时都会更新HTML .这里未说明的问题是两件事中的一件影响
aService.foo
,这样就不会发现变化 . 这两种可能性是:aService.foo
每次都被设置为一个新数组,导致对它的引用过时 .aService.foo
正在以更新时未触发$digest
周期的方式进行更新 .问题1:过时的参考文献
考虑到第一种可能性,假设正在应用
$digest
,如果aService.foo
始终是同一个数组,则自动设置$watch
将检测到更改,如下面的代码片段所示 .解决方案1-a:确保每次更新时数组或对象都是同一个对象
正如您所看到的,当
aService.foo
更改时,假设附加到aService.foo
的ng-repeat不会更新,但附加到aService2.foo
的ng-repeat会更新 . 这是因为我们对aService.foo
的引用已经过时,但我们对aService2.foo
的引用不是 . 我们使用$scope.foo = aService.foo;
创建了对初始数组的引用,然后服务在其下一次更新时将其丢弃,这意味着$scope.foo
不再引用我们想要的数组 .但是,虽然有几种方法可以确保初始引用保持一致,但有时可能需要更改对象或数组 . 或者如果服务属性引用像
String
或Number
这样的原语怎么办?在这些情况下,我们不能简单地依赖于参考 . 所以,我们能做些什么?之前给出的几个答案已经为这个问题提供了一些解决方案 . 但是,我个人赞成在评论中使用Jin和thetallweeks建议的简单方法:
解决方案1-b:将服务附加到范围,并在HTML中引用 . .
意思是,这样做:
HTML:
JS:
这样,
$watch
将在每个$digest
上解析aService.foo
,这将获得正确更新的值 .这是你试图用你的解决方法做的一种事情,但是方式要少得多 . 您在控制器中添加了一个不必要的
$watch
,只要它发生变化,它就会在$scope
上显式放置foo
. 将aService
而不是aService.foo
附加到$scope
时,您不需要额外的$watch
,并在标记中显式绑定到aService.foo
.现在,假设正在应用
$digest
循环,这一切都很好 . 在上面的示例中,我使用Angular的$interval服务来更新数组,这些数组会在每次更新后自动启动$digest
循环 . 但是,如果服务变量(无论出于何种原因)未在"Angular world"内更新,该怎么办?换句话说,只要服务属性发生变化,我们就不会自动激活$digest
循环?问题2:缺少$ digest
这里的许多解决方案都将解决这个问题,但我同意Code Whisperer:
因此,我更愿意继续在HTML标记中使用
aService.foo
引用,如上面的第二个示例所示,而不必在Controller中注册其他回调 .解决方案2:使用$ rootScope的setter和getter . $ apply()
我很惊讶没有人建议使用setter和getter . 这种能力是在ECMAScript5中引入的,因此已经存在多年了 . 当然,这意味着,无论出于何种原因,你需要支持真正的旧浏览器,那么这种方法将无法工作,但我觉得getter和setter在JavaScript中被大量使用 . 在这种特殊情况下,它们可能非常有用:
这里我在服务函数中添加了一个'private'变量:
realFoo
. 在service
对象上分别使用get foo()
和set foo()
函数更新和检索此get .注意在set函数中使用
$rootScope.$apply()
. 这可确保Angular知道对service.foo
的任何更改 . 如果出现'inprog'错误,请参阅this useful reference page,或者如果使用Angular> = 1.3,则可以使用$rootScope.$applyAsync()
.如果
aService.foo
经常更新,也要小心这一点,因为这可能会对性能产生重大影响 . 如果性能成为问题,您可以使用setter设置类似于其他答案的观察者模式 .您可以在$ rootScope中插入服务并观察:
在你的控制器中:
其他选项是使用外部库,如:https://github.com/melanke/Watch.JS
适用于:IE 9,FF 4,SF 5,WebKit,CH 7,OP 12,BESEN,Node.JS,Rhino 1.7
您可以观察一个,多个或所有对象属性的更改 .
例:
您可以观察工厂内部的变化,然后播出变化
然后在你的控制器中:
通过这种方式,您可以将所有相关的工厂代码放在其描述中,然后您只能依赖外部的广播
==UPDATED==
非常简单现在$ watch .
Pen here .
HTML:
JavaScript的:
在 dtheodor's 回答的基础上,您可以使用类似于下面的内容来确保您不会忘记取消注册回调...有些人可能会反对将
$scope
传递给服务 .Array.remove是一个扩展方法,如下所示:
这是我的通用方法 .
在你的控制器中
对于像我这样只想寻找简单解决方案的人来说,这几乎完全符合您对控制器中普通$ watch的期望 . 唯一的区别是,它在其javascript上下文中而不是在特定范围内评估字符串 . 您必须将$ rootScope注入您的服务,尽管它仅用于正确挂钩摘要周期 .
面对一个非常类似的问题,我在范围内看了一个函数,并让函数返回服务变量 . 我创建了一个js fiddle . 你可以在下面找到代码 .
我来到这个问题,但事实证明我的问题是,当我应该使用angular $ interval提供程序时,我正在使用setInterval . 这也是setTimeout的情况(改为使用$ timeout) . 我知道这不是OP的问题的答案,但它可能对一些人有所帮助,因为它帮助了我 .
我在另一个线程上找到了一个非常好的解决方案,它有类似的问题,但方法完全不同 . 资料来源:AngularJS : $watch within directive is not working when $rootScope value is changed
Basically 解决方案告诉 NOT TO 使用
$watch
因为它是非常重的解决方案 . Instead 他们建议使用$emit
和$on
.我的问题是在 service 中观察变量并在 directive 中做出反应 . 而且用上面的方法很容易!
我的模块/服务示例:
所以基本上我看着我的
user
- 每当它被设置为新值时$emit
一个user:change
状态 .在我的情况下,在我使用的指令中:
现在在指令中我听了
$rootScope
和 on 给定的变化 - 我分别做出反应 . 非常轻松优雅!//服务:(这里没什么特别的)
// ctrl:
有点难看,但我已经将范围变量的注册添加到我的服务中以进行切换:
然后在控制器中:
我在这里看到一些可怕的观察者模式导致大型应用程序的内存泄漏 .
我可能会有点迟,但这很简单 .
如果您想观看像数组推送之类的东西, Watch 功能会监视参考更改(原始类型):
someArray.push(someObj); someArray = someArray.splice(0);
这将更新参考并从任何地方更新 Watch . 包括服务getter方法 . 任何原始的东西都会自动更新 .
我迟到了,但我找到了比上面的答案更好的方法 . 我没有分配变量来保存服务变量的值,而是创建了一个附加到范围的函数,它返回服务变量 .
controller
我想这会做你想要的 . 我的控制器通过此实现继续检查我的服务的 Value . 老实说,这比选定的答案简单得多 .
我写了两个简单的实用程序服务,帮助我跟踪服务属性的变化 .
如果你想跳过很长的解释,你可以去海峡jsfiddle
此服务在控制器中以下列方式使用:假设您有一个服务“testService”,其属性为“prop1”,“prop2”,“prop3” . 您想要观察并分配范围'prop1'和'prop2' . 通过 Watch 服务,它看起来像这样:
我会在异步代码的末尾触发它以触发$ digest循环 . 像那样:
所以,所有这些看起来都是这样(你可以运行它或open fiddle):
看看这个plunker ::这是我能想到的最简单的例子
http://jsfiddle.net/HEdJF/