我有一个服务,说:
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;
}
});
}
这感觉很牵强,我一直在每个使用服务变量的控制器中重复它。有没有更好的方法来完成观察共享变量?
aService.foo
吗? (像这样:plnkr.co/edit/aNrw5Wo4Q0IxR2loipl5?p=preview)
如果您想避免 $watch
的暴政和开销,您总是可以使用良好的旧观察者模式。
在服务中:
factory('aService', function() {
var observerCallbacks = [];
//register an observer
this.registerObserverCallback = function(callback){
observerCallbacks.push(callback);
};
//call this when you know 'foo' has been changed
var notifyObservers = function(){
angular.forEach(observerCallbacks, function(callback){
callback();
});
};
//example of when you may want to notify observers
this.foo = someNgResource.query().$then(function(){
notifyObservers();
});
});
在控制器中:
function FooCtrl($scope, aService){
var updateFoo = function(){
$scope.foo = aService.foo;
};
aService.registerObserverCallback(updateFoo);
//service now in control of updating foo
};
在这种情况下,多个/未知对象可能对更改感兴趣,请使用正在更改的项目中的 $rootScope.$broadcast
。
而不是创建自己的侦听器注册表(必须在各种 $destroy 上清理),您应该能够从相关服务中$broadcast
。
您仍然必须在每个侦听器中编写 $on
处理程序,但该模式与对 $digest
的多次调用分离,从而避免了长时间运行的观察程序的风险。
这样,侦听器也可以从 DOM 和/或不同的子范围进出,而服务不会改变其行为。
** 更新:示例 **
广播在“全球”服务中最有意义,它可能会影响您应用程序中的无数其他事情。一个很好的例子是用户服务,其中可能发生许多事件,例如登录、注销、更新、空闲等。我相信这是广播最有意义的地方,因为任何范围都可以侦听事件,而无需甚至注入服务,它不需要评估任何表达式或缓存结果来检查更改。它只是触发并忘记(所以请确保它是触发后忘记通知,而不是需要采取行动的东西)
.factory('UserService', [ '$rootScope', function($rootScope) {
var service = <whatever you do for the object>
service.save = function(data) {
.. validate data and update model ..
// notify listeners and provide the data that changed [optional]
$rootScope.$broadcast('user:updated',data);
}
// alternatively, create a callback function and $broadcast from there if making an ajax call
return service;
}]);
当 save() 函数完成并且数据有效时,上面的服务将向每个范围广播一条消息。或者,如果它是 $resource 或 ajax 提交,请将广播调用移动到回调中,以便在服务器响应时触发。广播特别适合这种模式,因为每个侦听器只是等待事件,而不需要检查每个 $digest 的范围。监听器看起来像:
.controller('UserCtrl', [ 'UserService', '$scope', function(UserService, $scope) {
var user = UserService.getUser();
// if you don't want to expose the actual object in your scope you could expose just the values, or derive a value for your purposes
$scope.name = user.firstname + ' ' +user.lastname;
$scope.$on('user:updated', function(event,data) {
// you could inspect the data to see if what you care about changed, or just update your own scope
$scope.name = user.firstname + ' ' + user.lastname;
});
// different event names let you group your code and logic by what happened
$scope.$on('user:logout', function(event,data) {
.. do something differently entirely ..
});
}]);
这样做的好处之一是消除了多个手表。如果您像上面的示例那样组合字段或派生值,则必须同时查看 firstname 和 lastname 属性。观察 getUser() 函数只有在用户对象在更新时被替换时才会起作用,如果用户对象只是更新了它的属性,它就不会触发。在这种情况下,您必须进行深入观察,而且会更加密集。
$broadcast 将消息从它被调用的范围发送到任何子范围。因此,从 $rootScope 调用它会在每个范围内触发。例如,如果您要从控制器的作用域进行 $broadcast,它只会在从控制器作用域继承的作用域中触发。 $emit 则相反,其行为类似于 DOM 事件,因为它在作用域链上冒泡。
请记住,在某些情况下 $broadcast 很有意义,并且在某些情况下 $watch 是更好的选择 - 特别是在具有非常具体的 watch 表达式的隔离范围内。
我使用与@dtheodot 类似的方法,但使用角度承诺而不是传递回调
app.service('myService', function($q) {
var self = this,
defer = $q.defer();
this.foo = 0;
this.observeFoo = function() {
return defer.promise;
}
this.setFoo = function(foo) {
self.foo = foo;
defer.notify(self.foo);
}
})
然后只要使用 myService.setFoo(foo)
方法更新服务上的 foo
。在您的控制器中,您可以将其用作:
myService.observeFoo().then(null, null, function(foo){
$scope.foo = foo;
})
then
的前两个参数是成功和错误回调,第三个是通知回调。
$scope.$watch
似乎不起作用(我正在观看的范围是从 $rootScope
继承的模态) - 这有效。很酷的技巧,谢谢分享!
没有手表或观察者回调 (http://jsfiddle.net/zymotik/853wvv7s/):
JavaScript:
angular.module("Demo", [])
.factory("DemoService", function($timeout) {
function DemoService() {
var self = this;
self.name = "Demo Service";
self.count = 0;
self.counter = function(){
self.count++;
$timeout(self.counter, 1000);
}
self.addOneHundred = function(){
self.count+=100;
}
self.counter();
}
return new DemoService();
})
.controller("DemoController", function($scope, DemoService) {
$scope.service = DemoService;
$scope.minusOneHundred = function() {
DemoService.count -= 100;
}
});
HTML
<div ng-app="Demo" ng-controller="DemoController">
<div>
<h4>{{service.name}}</h4>
<p>Count: {{service.count}}</p>
</div>
</div>
当我们从服务传回一个对象而不是一个值时,这个 JavaScript 就起作用了。当从服务返回一个 JavaScript 对象时,Angular 会将监视添加到它的所有属性中。
另请注意,我使用 'var self = this' 因为我需要在 $timeout 执行时保留对原始对象的引用,否则 'this' 将引用窗口对象。
$scope.count = service.count
是行不通的。
$scope.data = service.data
<p>Count: {{ data.count }}</p>
我偶然发现了这个问题,正在寻找类似的东西,但我认为它值得对正在发生的事情进行彻底的解释,以及一些额外的解决方案。
当 HTML 中出现您使用的 Angular 表达式时,Angular 会自动为 $scope.foo
设置一个 $watch
,并在 $scope.foo
更改时更新 HTML。
<div ng-controller="FooCtrl">
<div ng-repeat="item in foo">{{ item }}</div>
</div>
此处未说明的问题是影响 aService.foo
的两件事之一,因此未检测到更改。这两种可能性是:
aService.foo 每次都被设置为一个新数组,导致对它的引用已过时。 aService.foo 的更新方式不会在更新时触发 $digest 循环。
问题 1:过时的引用
考虑第一种可能性,假设正在应用 $digest
,如果 aService.foo
始终是同一个数组,则自动设置的 $watch
将检测到更改,如下面的代码片段所示。
解决方案 1-a:确保数组或对象在每次更新时都是同一个对象
angular.module('myApp', []) .factory('aService', [ '$interval', function($interval) { var service = { foo: [] }; // 在每次更新时创建一个新数组,附加前面的项目并 // 每次添加一个新项目 $interval(function() { if (service.foo.length < 10) { var newArray = [] Array.prototype.push.apply(newArray, service.foo) ; newArray.push(Math.random()); service.foo = newArray; } }, 1000); return service; } ]) .factory('aService2', [ '$interval', function($interval) { var service = { foo: [] }; // 保持相同的数组,每次更新时添加新项目 $interval(function() { if (service.foo.length < 10) { service.foo.push(Math.random ()); } }, 1000); 返回服务; } ]) .controller('FooCtrl', [ '$scope', 'aService', 'aService2', function FooCtrl($scope, aService, aService2) { $scope .foo = aService.foo; $scope.foo2 = aService2.foo; } ]);
如您所见,当 aService.foo
更改时,应该附加到 aService.foo
的 ng-repeat 不会更新,但附加到 aService2.foo
的 ng-repeat 会。这是因为我们对 aService.foo
的引用已经过时,但我们对 aService2.foo
的引用却没有。我们使用 $scope.foo = aService.foo;
创建了对初始数组的引用,然后服务在下一次更新时将其丢弃,这意味着 $scope.foo
不再引用我们想要的数组。
然而,虽然有多种方法可以确保初始引用保持完整,但有时可能需要更改对象或数组。或者,如果服务属性引用像 String
或 Number
这样的原语怎么办?在这些情况下,我们不能简单地依赖参考。那么我们可以做什么呢?
之前给出的几个答案已经为该问题提供了一些解决方案。不过,我个人更赞成在评论中使用 Jin 和 thetallweeks 建议的简单方法:
只需在 html 标记中引用 aService.foo
解决方案 1-b:将服务附加到范围,并在 HTML 中引用 {service}.{property}。
意思是,只需这样做:
HTML:
<div ng-controller="FooCtrl">
<div ng-repeat="item in aService.foo">{{ item }}</div>
</div>
JS:
function FooCtrl($scope, aService) {
$scope.aService = aService;
}
angular.module('myApp', []) .factory('aService', [ '$interval', function($interval) { var service = { foo: [] }; // 在每次更新时创建一个新数组,附加前面的项目并 // 每次添加一个新项目 $interval(function() { if (service.foo.length < 10) { var newArray = [] Array.prototype.push.apply(newArray, service.foo) ; newArray.push(Math.random()); service.foo = newArray; } }, 1000); 返回服务; } ]) .controller('FooCtrl', [ '$scope', 'aService', function FooCtrl( $scope, aService) { $scope.aService = aService; } ]);
这样,$watch
将解析每个 $digest
上的 aService.foo
,从而获得正确更新的值。
这就是你试图用你的解决方法做的事情,但以一种少得多的方式。您在控制器中添加了一个不必要的 $watch
,该控制器在其更改时将 foo
显式放置在 $scope
上。当您将 aService
而不是 aService.foo
附加到 $scope
并在标记中显式绑定到 aService.foo
时,您不需要额外的 $watch
。
现在假设正在应用 $digest
循环,这一切都很好。在上面的示例中,我使用了 Angular 的 $interval
服务来更新数组,它会在每次更新后自动启动 $digest
循环。但是,如果服务变量(无论出于何种原因)没有在“Angular 世界”中得到更新怎么办。换句话说,我们不有一个$digest
循环在服务属性更改时自动激活?
问题 2:缺少 $digest
这里的许多解决方案都可以解决这个问题,但我同意 Code Whisperer:
我们使用像 Angular 这样的框架的原因是为了不编写我们自己的观察者模式
因此,我更愿意继续在 HTML 标记中使用 aService.foo
引用,如上面第二个示例所示,而不必在 Controller 中注册额外的回调。
解决方案 2:使用带有 $rootScope.$apply() 的 setter 和 getter
我很惊讶还没有人建议使用 setter 和 getter。此功能是在 ECMAScript5 中引入的,因此已经存在多年。当然,这意味着无论出于何种原因,如果您需要支持非常旧的浏览器,那么这种方法将不起作用,但我觉得 getter 和 setter 在 JavaScript 中的使用严重不足。在这种特殊情况下,它们可能非常有用:
factory('aService', [
'$rootScope',
function($rootScope) {
var realFoo = [];
var service = {
set foo(a) {
realFoo = a;
$rootScope.$apply();
},
get foo() {
return realFoo;
}
};
// ...
}
angular.module('myApp', []) .factory('aService', [ '$rootScope', function($rootScope) { var realFoo = []; var service = { set foo(a) { realFoo = a; $rootScope.$apply(); }, get foo() { return realFoo; } }; // 在每次更新时创建一个新数组,附加之前的项和 // 每次添加一个新项 setInterval(function() { if (service.foo.length < 10) { var newArray = []; Array.prototype.push.apply(newArray, service.foo); newArray.push(Math.random()); service.foo = newArray; } }, 1000); 返回服务; } ]) .controller('FooCtrl', [ '$scope', 'aService', function FooCtrl($scope, aService) { $scope.aService = aService; } ]);
在这里,我在服务函数中添加了一个“私有”变量: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 设置类似于此处其他答案的观察者模式。
services
而不是属于服务本身的属性。
据我所知,您不必做那么精细的事情。您已经将服务中的 foo 分配给了您的范围,并且因为 foo 是一个数组(反过来,它是一个通过引用分配的对象!)。所以,你需要做的就是这样的:
function FooCtrl($scope, aService) {
$scope.foo = aService.foo;
}
如果同一个 Ctrl 中的一些其他变量依赖于 foo 的变化,那么是的,您需要一个手表来观察 foo 并对该变量进行更改。但只要是简单的参考,观看是没有必要的。希望这可以帮助。
$watch
使用原语。相反,我在服务上定义了一个返回原始值的方法:somePrimitive() = function() { return somePrimitive }
,并为该方法分配了一个 $scope 属性:$scope.somePrimitive = aService.somePrimitive;
。然后我在 HTML 中使用了 scope 方法:<span>{{somePrimitive()}}</span>
$scope.foo = aService.foo
不会自动更新范围变量。
您可以在 $rootScope 中插入服务并观看:
myApp.run(function($rootScope, aService){
$rootScope.aService = aService;
$rootScope.$watch('aService', function(){
alert('Watch');
}, true);
});
在您的控制器中:
myApp.controller('main', function($scope){
$scope.aService.foo = 'change';
});
其他选项是使用外部库,例如:https://github.com/melanke/Watch.JS
适用于:IE 9+、FF 4+、SF 5+、WebKit、CH 7+、OP 12+、BESEN、Node.JS、Rhino 1.7+
您可以观察到一个、多个或所有对象属性的变化。
例子:
var ex3 = {
attr1: 0,
attr2: "initial value of attr2",
attr3: ["a", 3, null]
};
watch(ex3, function(){
alert("some attribute of ex3 changes!");
});
ex3.attr3.push("new value");
您可以在工厂内部观看更改,然后广播更改
angular.module('MyApp').factory('aFactory', function ($rootScope) {
// Define your factory content
var result = {
'key': value
};
// add a listener on a key
$rootScope.$watch(function () {
return result.key;
}, function (newValue, oldValue, scope) {
// This is called after the key "key" has changed, a good idea is to broadcast a message that key has changed
$rootScope.$broadcast('aFactory:keyChanged', newValue);
}, true);
return result;
});
然后在你的控制器中:
angular.module('MyApp').controller('aController', ['$rootScope', function ($rootScope) {
$rootScope.$on('aFactory:keyChanged', function currentCityChanged(event, value) {
// do something
});
}]);
通过这种方式,您将所有相关的工厂代码放在其描述中,然后您只能依赖来自外部的广播
==更新==
现在在 $watch 中非常简单。
HTML:
<div class="container" data-ng-app="app">
<div class="well" data-ng-controller="FooCtrl">
<p><strong>FooController</strong></p>
<div class="row">
<div class="col-sm-6">
<p><a href="" ng-click="setItems([ { name: 'I am single item' } ])">Send one item</a></p>
<p><a href="" ng-click="setItems([ { name: 'Item 1 of 2' }, { name: 'Item 2 of 2' } ])">Send two items</a></p>
<p><a href="" ng-click="setItems([ { name: 'Item 1 of 3' }, { name: 'Item 2 of 3' }, { name: 'Item 3 of 3' } ])">Send three items</a></p>
</div>
<div class="col-sm-6">
<p><a href="" ng-click="setName('Sheldon')">Send name: Sheldon</a></p>
<p><a href="" ng-click="setName('Leonard')">Send name: Leonard</a></p>
<p><a href="" ng-click="setName('Penny')">Send name: Penny</a></p>
</div>
</div>
</div>
<div class="well" data-ng-controller="BarCtrl">
<p><strong>BarController</strong></p>
<p ng-if="name">Name is: {{ name }}</p>
<div ng-repeat="item in items">{{ item.name }}</div>
</div>
</div>
JavaScript:
var app = angular.module('app', []);
app.factory('PostmanService', function() {
var Postman = {};
Postman.set = function(key, val) {
Postman[key] = val;
};
Postman.get = function(key) {
return Postman[key];
};
Postman.watch = function($scope, key, onChange) {
return $scope.$watch(
// This function returns the value being watched. It is called for each turn of the $digest loop
function() {
return Postman.get(key);
},
// This is the change listener, called when the value returned from the above function changes
function(newValue, oldValue) {
if (newValue !== oldValue) {
// Only update if the value changed
$scope[key] = newValue;
// Run onChange if it is function
if (angular.isFunction(onChange)) {
onChange(newValue, oldValue);
}
}
}
);
};
return Postman;
});
app.controller('FooCtrl', ['$scope', 'PostmanService', function($scope, PostmanService) {
$scope.setItems = function(items) {
PostmanService.set('items', items);
};
$scope.setName = function(name) {
PostmanService.set('name', name);
};
}]);
app.controller('BarCtrl', ['$scope', 'PostmanService', function($scope, PostmanService) {
$scope.items = [];
$scope.name = '';
PostmanService.watch($scope, 'items');
PostmanService.watch($scope, 'name', function(newVal, oldVal) {
alert('Hi, ' + newVal + '!');
});
}]);
在 dtheodor 的 回答的基础上,您可以使用类似于以下内容的方法来确保您不会忘记取消注册回调...不过,有些人可能会反对将 $scope
传递给服务。
factory('aService', function() {
var observerCallbacks = [];
/**
* Registers a function that will be called when
* any modifications are made.
*
* For convenience the callback is called immediately after registering
* which can be prevented with `preventImmediate` param.
*
* Will also automatically unregister the callback upon scope destory.
*/
this.registerObserver = function($scope, cb, preventImmediate){
observerCallbacks.push(cb);
if (preventImmediate !== true) {
cb();
}
$scope.$on('$destroy', function () {
observerCallbacks.remove(cb);
});
};
function notifyObservers() {
observerCallbacks.forEach(function (cb) {
cb();
});
};
this.foo = someNgResource.query().$then(function(){
notifyObservers();
});
});
Array.remove 是一个扩展方法,如下所示:
/**
* Removes the given item the current array.
*
* @param {Object} item The item to remove.
* @return {Boolean} True if the item is removed.
*/
Array.prototype.remove = function (item /*, thisp */) {
var idx = this.indexOf(item);
if (idx > -1) {
this.splice(idx, 1);
return true;
}
return false;
};
这是我的通用方法。
mainApp.service('aService',[function(){
var self = this;
var callbacks = {};
this.foo = '';
this.watch = function(variable, callback) {
if (typeof(self[variable]) !== 'undefined') {
if (!callbacks[variable]) {
callbacks[variable] = [];
}
callbacks[variable].push(callback);
}
}
this.notifyWatchersOn = function(variable) {
if (!self[variable]) return;
if (!callbacks[variable]) return;
angular.forEach(callbacks[variable], function(callback, key){
callback(self[variable]);
});
}
this.changeFoo = function(newValue) {
self.foo = newValue;
self.notifyWatchersOn('foo');
}
}]);
在你的控制器中
function FooCtrl($scope, aService) {
$scope.foo;
$scope._initWatchers = function() {
aService.watch('foo', $scope._onFooChange);
}
$scope._onFooChange = function(newValue) {
$scope.foo = newValue;
}
$scope._initWatchers();
}
FooCtrl.$inject = ['$scope', 'aService'];
对于像我这样只是在寻找简单解决方案的人来说,这几乎完全符合您在控制器中使用普通 $watch 的期望。唯一的区别是,它在它的 javascript 上下文中而不是在特定范围内评估字符串。您必须将 $rootScope 注入您的服务,尽管它仅用于正确连接到摘要周期。
function watch(target, callback, deep) {
$rootScope.$watch(function () {return eval(target);}, callback, deep);
};
在面临一个非常相似的问题时,我在范围内观察了一个函数并让该函数返回服务变量。我创建了一个 js fiddle。您可以在下面找到代码。
var myApp = angular.module("myApp",[]);
myApp.factory("randomService", function($timeout){
var retValue = {};
var data = 0;
retValue.startService = function(){
updateData();
}
retValue.getData = function(){
return data;
}
function updateData(){
$timeout(function(){
data = Math.floor(Math.random() * 100);
updateData()
}, 500);
}
return retValue;
});
myApp.controller("myController", function($scope, randomService){
$scope.data = 0;
$scope.dataUpdated = 0;
$scope.watchCalled = 0;
randomService.startService();
$scope.getRandomData = function(){
return randomService.getData();
}
$scope.$watch("getRandomData()", function(newValue, oldValue){
if(oldValue != newValue){
$scope.data = newValue;
$scope.dataUpdated++;
}
$scope.watchCalled++;
});
});
我遇到了这个问题,但事实证明我的问题是我应该使用 angular $interval 提供程序时使用了 setInterval。这也是 setTimeout 的情况(使用 $timeout 代替)。我知道这不是 OP 问题的答案,但它可能对一些人有所帮助,因为它帮助了我。
setTimeout
或任何其他非 Angular 函数,但不要忘记使用 $scope.$apply()
将代码包装在回调中。
我在另一个有类似问题但方法完全不同的线程上找到了一个非常好的解决方案。来源:AngularJS : $watch within directive is not working when $rootScope value is changed
基本上那里的解决方案告诉 不要使用 $watch
,因为它是非常重的解决方案。 改为,他们建议使用 $emit
和 $on
。
我的问题是在我的服务中观察一个变量并在指令中做出反应。而且用上面的方法就很简单了!
我的模块/服务示例:
angular.module('xxx').factory('example', function ($rootScope) {
var user;
return {
setUser: function (aUser) {
user = aUser;
$rootScope.$emit('user:change');
},
getUser: function () {
return (user) ? user : false;
},
...
};
});
所以基本上我观看我的 user
- 每当它设置为新值时,我 $emit
就处于 user:change
状态。
现在就我而言,在我使用的指令中:
angular.module('xxx').directive('directive', function (Auth, $rootScope) {
return {
...
link: function (scope, element, attrs) {
...
$rootScope.$on('user:change', update);
}
};
});
现在,在 directive 中,我聆听 $rootScope
和 on 给定的变化 - 我分别做出反应。非常轻松优雅!
// 服务:(这里没什么特别的)
myApp.service('myService', function() {
return { someVariable:'abc123' };
});
// 控制:
myApp.controller('MyCtrl', function($scope, myService) {
$scope.someVariable = myService.someVariable;
// watch the service and update this ctrl...
$scope.$watch(function(){
return myService.someVariable;
}, function(newValue){
$scope.someVariable = newValue;
});
});
有点难看,但我已经在我的服务中添加了范围变量的注册以进行切换:
myApp.service('myService', function() {
var self = this;
self.value = false;
self.c2 = function(){};
self.callback = function(){
self.value = !self.value;
self.c2();
};
self.on = function(){
return self.value;
};
self.register = function(obj, key){
self.c2 = function(){
obj[key] = self.value;
obj.$apply();
}
};
return this;
});
然后在控制器中:
function MyCtrl($scope, myService) {
$scope.name = 'Superhero';
$scope.myVar = false;
myService.register($scope, 'myVar');
}
this
而不是 self
?
return this;
出你的构造函数的好习惯 ;-)
看看这个 plunker:: 这是我能想到的最简单的例子
<div ng-app="myApp">
<div ng-controller="FirstCtrl">
<input type="text" ng-model="Data.FirstName"><!-- Input entered here -->
<br>Input is : <strong>{{Data.FirstName}}</strong><!-- Successfully updates here -->
</div>
<hr>
<div ng-controller="SecondCtrl">
Input should also be here: {{Data.FirstName}}<!-- How do I automatically updated it here? -->
</div>
</div>
// declare the app with no dependencies
var myApp = angular.module('myApp', []);
myApp.factory('Data', function(){
return { FirstName: '' };
});
myApp.controller('FirstCtrl', function( $scope, Data ){
$scope.Data = Data;
});
myApp.controller('SecondCtrl', function( $scope, Data ){
$scope.Data = Data;
});
我在这里看到了一些可怕的观察者模式,它们会导致大型应用程序出现内存泄漏。
我可能晚了一点,但就这么简单。
如果您想查看类似数组推送的内容,watch 函数会查看引用更改(原始类型),只需使用:
someArray.push(someObj); someArray = someArray.splice(0);
这将更新参考并从任何地方更新手表。包括一个服务获取方法。任何原始的东西都会自动更新。
我迟到了,但我找到了比上面发布的答案更好的方法。我没有分配一个变量来保存服务变量的值,而是创建了一个附加到范围的函数,它返回服务变量。
控制器
$scope.foo = function(){
return aService.foo;
}
我认为这会做你想要的。我的控制器通过这个实现不断检查我的服务的价值。老实说,这比选择的答案要简单得多。
我编写了两个简单的实用程序服务来帮助我跟踪服务属性的变化。
如果你想跳过冗长的解释,你可以直奔jsfiddle
观察对象
mod.service('WatchObj', ['$rootScope', WatchObjService]); function WatchObjService($rootScope) { // 返回监视函数 // obj:要监视的对象 // fields:要监视的字段数组 // target:分配更改的位置(通常是 $scope 或控制器实例) // $scope:可选,如果没有提供 $rootScope 则使用 return function watch_obj(obj, fields, target, $scope) { $scope = $scope || $根范围; //初始化手表并创建一个“unwatch 函数”数组 var watch = fields.map(function(field) { return $scope.$watch( function() { return obj[field]; }, function(new_val) { target [字段] = new_val; } ); }); //注销函数将注销我们所有的手表 var unregister = function unregister_watch_obj() { watch.map(function(unregister) { unregister(); }); }; //作用域被销毁时自动注销 $scope.$on('$destroy', unregister);返回注销; }; }
该服务在控制器中的使用方式如下:假设您有一个服务“testService”,其属性为“prop1”、“prop2”、“prop3”。您想观察并分配给范围“prop1”和“prop2”。使用 watch 服务,它看起来像这样:
app.controller('TestWatch', ['$scope', 'TestService', 'WatchObj', TestWatchCtrl]);功能 TestWatchCtrl($scope, testService, watch) { $scope.prop1 = testService.prop1; $scope.prop2 = testService.prop2; $scope.prop3 = testService.prop3; watch(testService, ['prop1', 'prop2'], $scope, $scope); }
apply Watch obj 很棒,但如果您的服务中有异步代码,这还不够。对于这种情况,我使用第二个实用程序,如下所示:
mod.service('apply', ['$timeout', ApplyService]);函数 ApplyService($timeout) { 返回函数 apply() { $timeout(function() {}); }; }
我会在我的异步代码末尾触发它来触发 $digest 循环。像那样:
app.service('TestService', ['apply', TestService]);功能 TestService(apply) { this.apply = apply; } TestService.prototype.test3 = function() { setTimeout(function() { this.prop1 = 'changed_test_2'; this.prop2 = 'changed2_test_2'; this.prop3 = 'changed3_test_2'; this.apply(); //触发器$digest 循环 }.bind(this)); }
所以,所有这些看起来像这样(您可以运行它或 open fiddle):
// 测试应用代码 var app = angular.module('app', ['watch_utils']); app.controller('TestWatch', ['$scope', 'TestService', 'WatchObj', TestWatchCtrl]);功能 TestWatchCtrl($scope, testService, watch) { $scope.prop1 = testService.prop1; $scope.prop2 = testService.prop2; $scope.prop3 = testService.prop3; watch(testService, ['prop1', 'prop2'], $scope, $scope); $scope.test1 = function() { testService.test1(); }; $scope.test2 = function() { testService.test2(); }; $scope.test3 = function() { testService.test3(); }; } app.service('TestService', ['apply', TestService]);功能 TestService(apply) { this.apply = apply; this.reset(); } TestService.prototype.reset = function() { this.prop1 = 'unchenged'; this.prop2 = 'unchenged2'; this.prop3 = 'unchenged3'; } TestService.prototype.test1 = function() { this.prop1 = 'changed_test_1'; this.prop2 = 'changed2_test_1'; this.prop3 = 'changed3_test_1'; } TestService.prototype.test2 = function() { setTimeout(function() { this.prop1 = 'changed_test_2'; this.prop2 = 'changed2_test_2'; this.prop3 = 'changed3_test_2'; }.bind(this)); } TestService.prototype.test3 = function() { setTimeout(function() { this.prop1 = 'changed_test_2'; this.prop2 = 'changed2_test_2'; this.prop3 = 'changed3_test_2'; this.apply(); }.bind (这个)); } //结束测试应用程序代码 //WATCH UTILS var mod = angular.module('watch_utils', []); mod.service('apply', ['$timeout', ApplyService]);函数 ApplyService($timeout) { 返回函数 apply() { $timeout(function() {}); }; } mod.service('WatchObj', ['$rootScope', WatchObjService]); function WatchObjService($rootScope) { // target 并不总是等于 $scope,例如在 //directives 中使用 bindToController 语法时 return function watch_obj(obj, fields, target, $scope) { // 如果 $scope 没有提供,$使用 rootScope $scope = $scope || $根范围; var watch = fields.map(function(field) { return $scope.$watch( function() { return obj[field]; }, function(new_val) { target[field] = new_val; } ); }); var unregister = function unregister_watch_obj() { watch.map(function(unregister) { unregister(); }); }; $scope.$on('$destroy', 注销);返回注销; }; }
$destory
事件并向aService
添加取消注册方法$watch
vs观察者模式只是选择是轮询还是推送,基本上是性能问题,所以在性能问题时使用它。我使用观察者模式,否则我将不得不“深入”观察复杂的对象。我将整个服务附加到 $scope 而不是查看单个服务值。我避免像魔鬼一样使用 Angular 的 $watch,在指令和原生 Angular 数据绑定中发生的事情已经足够多。