如何监视服务变量?

我有一个服务,说:

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

这感觉有点冗长,我一直在每个使用服务变量的控制器中重复这一点。有没有更好的方法来监视共享变量?

223194 次浏览

据我所知,你不需要做那么复杂的事情。您已经将foo从服务分配到作用域,并且由于foo是一个数组(反过来又是一个对象,它是通过引用分配的!). 所以,你所需要做的就是这样:

function FooCtrl($scope, aService) {
$scope.foo = aService.foo;


}

如果这个Ctrl中的其他变量依赖于foo的变化,那么是的,你需要一个手表来观察foo并对该变量进行更改。但只要是简单的参考观看是不必要的。希望这能有所帮助。

有点丑,但我已经为切换添加了范围变量的注册到我的服务:

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

如果你想避免$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
};

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

数组中。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'];

在这样的场景中,多个/未知对象可能对更改感兴趣,请使用被更改项中的$rootScope.$broadcast

而不是创建自己的侦听器注册表(必须在各种$ destroyed上清理),你应该能够从相关服务中$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提交,则将广播调用移动到回调中,以便在服务器响应时触发它。广播特别适合这种模式,因为每个侦听器只需要等待事件,而不需要检查每个$摘要上的作用域。监听器看起来是这样的:

.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的前两个参数是成功回调和错误回调,第三个参数是通知回调。

$q的参考

你可以在$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");​
对于那些像我一样只是寻找一个简单的解决方案,这几乎完全是你所期望的使用正常的$watch在控制器中。 唯一的区别是,它在javascript上下文中计算字符串,而不是在特定的作用域上。你必须将$rootScope注入到你的服务中,尽管它只用于正确地钩子到摘要周期中
function watch(target, callback, deep) {
$rootScope.$watch(function () {return eval(target);}, callback, deep);
};

您可以在工厂内部观察更改,然后广播更改

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

通过这种方式,您将所有相关的工厂代码放在其描述中,然后您只能依赖来自外部的广播

当我面对一个非常相似的问题时,我观察了一个作用域中的函数,并让函数返回服务变量。我已经创建了js小提琴。您可以在下面找到代码。

    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问题的答案,但它可能会有所帮助,就像它帮助了我一样。

没有手表或观察者回调(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;
}


});

超文本标记语言

<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'将引用窗口对象。

我在另一个线程上找到了一个非常好的解决方案,有类似的问题,但方法完全不同。来源:AngularJS:当$rootScope值被改变时,指令中的$watch将不起作用

基本上的解决方案告诉使用$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;
},
...
};
});

所以基本上我 my user -每当它被设置为新值时,我$emit a user:change状态。

现在在我的例子中,在指令中我使用:

angular.module('xxx').directive('directive', function (Auth, $rootScope) {
return {
...
link: function (scope, element, attrs) {
...
$rootScope.$on('user:change', update);
}
};
});

现在在指令中,我在$rootScope上监听给定的变化-我分别做出反应。非常简单和优雅!

= = = =更新

在$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 + '!');
});
}]);

我在寻找类似的东西时偶然发现了这个问题,但我认为它值得对正在发生的事情进行彻底的解释,以及一些额外的解决方案。

当HTML中出现了你使用的angular表达式时,angular会自动为$scope.foo设置一个$watch,并在$scope.foo发生变化时更新HTML。

<div ng-controller="FooCtrl">
<div ng-repeat="item in foo">\{\{ item }}</div>
</div>

这里没有说的问题是,有两件事之一正在影响aService.foo,这样更改就不会被检测到。这两种可能性是:

  1. aService.foo每次都被设置为一个新数组,导致对它的引用过期。
  2. aService.foo的更新方式是在更新时不会触发$digest循环。

问题1:过时的引用

考虑第一种可能性,假设应用了$digest,如果aService.foo始终是相同的数组,则自动设置的$watch将检测到更改,如下面的代码片段所示。

解决方案1-a:确保数组或对象在每次更新时为相同的对象

angular.module('myApp', [])
.factory('aService', [
'$interval',
function($interval) {
var service = {
foo: []
};


// Create a new array on each update, appending the previous items and
// adding one new item each time
$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: []
};


// Keep the same array, just add new items on each update
$interval(function() {
if (service.foo.length < 10) {
service.foo.push(Math.random());
}
}, 1000);


return service;
}
])
.controller('FooCtrl', [
'$scope',
'aService',
'aService2',
function FooCtrl($scope, aService, aService2) {
$scope.foo = aService.foo;
$scope.foo2 = aService2.foo;
}
]);
<!DOCTYPE html>
<html>


<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<link rel="stylesheet" href="style.css" />
<script src="script.js"></script>
</head>


<body ng-app="myApp">
<div ng-controller="FooCtrl">
<h1>Array changes on each update</h1>
<div ng-repeat="item in foo">\{\{ item }}</div>
<h1>Array is the same on each udpate</h1>
<div ng-repeat="item in foo2">\{\{ item }}</div>
</div>
</body>


</html>

正如你所看到的,当aService.foo改变时,附加在aService.foo上的ng-repeat不会更新,但是附加在aService2.foo上的ng-repeat会更新。这是因为对aService.foo的引用过时了,但对aService2.foo的引用没有过时。我们用$scope.foo = aService.foo;创建了对初始数组的引用,该引用在服务下次更新时被丢弃,这意味着$scope.foo不再引用我们想要的数组。

然而,虽然有几种方法可以确保初始引用保持适当,但有时可能需要更改对象或数组。或者如果服务属性引用像StringNumber这样的原语怎么办?在这种情况下,我们不能仅仅依靠参考资料。那么我们做什么可以呢?

前面给出的几个答案已经给出了这个问题的一些解决方案。然而,我个人倾向于使用评论中thetallweeks所建议的简单方法:

引用aService即可。Foo在HTML标记中

解决方案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: []
};


// Create a new array on each update, appending the previous items and
// adding one new item each time
$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;
}
])
.controller('FooCtrl', [
'$scope',
'aService',
function FooCtrl($scope, aService) {
$scope.aService = aService;
}
]);
<!DOCTYPE html>
<html>


<head>
<script data-require="angular.js@1.4.7" data-semver="1.4.7" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.7/angular.js"></script>
<link rel="stylesheet" href="style.css" />
<script src="script.js"></script>
</head>


<body ng-app="myApp">
<div ng-controller="FooCtrl">
<h1>Array changes on each update</h1>
<div ng-repeat="item in aService.foo">\{\{ item }}</div>
</div>
</body>


</html>

这样,$watch将解析每个$digest上的aService.foo,从而得到正确更新的值。

这就是你想要的变通方法,但是不那么迂回。你在控制器中添加了一个不必要的$watch,它会在$scope发生变化时显式地将foo放在$scope上。当你将aService而不是aService.foo附加到$scope,并在标记中显式绑定到aService.foo时,你不需要额外的$watch


现在这一切都很好,假设应用了$digest循环。在上面的例子中,我使用了Angular的$interval服务来更新数组,它会在每次更新后自动启动$digest循环。但是,如果服务变量(不管出于什么原因)在“Angular世界”中没有得到更新呢?换句话说,我们有一个$digest周期被自动激活每当服务属性改变?


问题2:缺少$digest

这里的许多解决方案将解决这个问题,但我同意代码窃窃私语的人:

我们使用Angular这样的框架的原因是不需要自己编造观察者模式

因此,我宁愿继续在HTML标记中使用aService.foo引用,如上面的第二个例子所示,而不必在控制器中注册一个额外的回调。

解决方案2:在$rootScope.$apply()中使用setter和getter

我很惊讶还没有人建议使用settergetter。这个功能是在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;
}
};


// Create a new array on each update, appending the previous items and
// adding one new item each time
setInterval(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;
}
])
.controller('FooCtrl', [
'$scope',
'aService',
function FooCtrl($scope, aService) {
$scope.aService = aService;
}
]);
<!DOCTYPE html>
<html>


<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<link rel="stylesheet" href="style.css" />
<script src="script.js"></script>
</head>


<body ng-app="myApp">
<div ng-controller="FooCtrl">
<h1>Using a Getter/Setter</h1>
<div ng-repeat="item in aService.foo">\{\{ item }}</div>
</div>
</body>


</html>

这里我在服务函数中添加了一个“私有”变量:realFoo。这个get分别使用service对象上的get foo()set foo()函数更新和检索。

注意在set函数中使用了$rootScope.$apply()。这确保了Angular能察觉到service.foo的任何变化。如果你得到'inprog'错误,请参阅这个有用的参考页面,或者如果你使用Angular >= 1.3,你可以直接使用$rootScope.$applyAsync()

如果aService.foo非常频繁地更新,也要注意这一点,因为这可能会严重影响性能。如果性能是一个问题,您可以使用setter设置一个类似于这里其他答案的观察者模式。

我在这里看到过一些可怕的观察者模式,它们会在大型应用程序上导致内存泄漏。

我可能会来晚一点,但就是这么简单。

如果你想查看数组推送之类的东西,可以使用watch函数查看引用更改(基本类型):

someArray.push(someObj); someArray = someArray.splice(0);

这将更新引用并从任何地方更新手表。包括一个服务getter方法。 任何原语都将被自动更新

// service:(这里没有什么特别的)

myApp.service('myService', function() {
return { someVariable:'abc123' };
});

/ / ctrl:

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

我迟到了,但我找到了一个比上面的答案更好的方法。我没有分配一个变量来保存服务变量的值,而是创建了一个附加到作用域的函数,该函数返回服务变量。

控制器

$scope.foo = function(){
return aService.foo;
}

我觉得这能满足你的要求。我的控制器通过这个实现不断检查我的服务的值。老实说,这比选择的答案要简单得多。

我编写了两个简单的实用程序服务,它们帮助我跟踪服务属性的更改。

如果你想跳过冗长的解释,你可以直接到jsfiddle

  1. WatchObj

mod.service('WatchObj', ['$rootScope', WatchObjService]);


function WatchObjService($rootScope) {
// returns watch function
// obj: the object to watch for
// fields: the array of fields to watch
// target: where to assign changes (usually it's $scope or controller instance)
// $scope: optional, if not provided $rootScope is use
return function watch_obj(obj, fields, target, $scope) {
$scope = $scope || $rootScope;
//initialize watches and create an array of "unwatch functions"
var watched = fields.map(function(field) {
return $scope.$watch(
function() {
return obj[field];
},
function(new_val) {
target[field] = new_val;
}
);
});
//unregister function will unregister all our watches
var unregister = function unregister_watch_obj() {
watched.map(function(unregister) {
unregister();
});
};
//automatically unregister when scope is destroyed
$scope.$on('$destroy', unregister);
return unregister;
};
}

该服务在控制器上的作用如下: 假设你有一个服务“testService”,它的属性是“prop1”,“prop2”,“prop3”。您希望监视并将其分配给范围“prop1”和“prop2”。对于手表服务,它看起来像这样:

app.controller('TestWatch', ['$scope', 'TestService', 'WatchObj', TestWatchCtrl]);


function TestWatchCtrl($scope, testService, watch) {
$scope.prop1 = testService.prop1;
$scope.prop2 = testService.prop2;
$scope.prop3 = testService.prop3;
watch(testService, ['prop1', 'prop2'], $scope, $scope);
}

    <李>应用 Watch obj很棒,但如果您的服务中有异步代码,它就不够了。对于这种情况,我使用了第二个实用程序,如下所示:

mod.service('apply', ['$timeout', ApplyService]);


function ApplyService($timeout) {
return function apply() {
$timeout(function() {});
};
}

我将在异步代码的末尾触发它来触发$digest循环。 像这样:

app.service('TestService', ['apply', TestService]);


function 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(); //trigger $digest loop
}.bind(this));
}

所以,所有这些加在一起就像这样(你可以运行它或开放的小提琴):

// TEST app code


var app = angular.module('app', ['watch_utils']);


app.controller('TestWatch', ['$scope', 'TestService', 'WatchObj', TestWatchCtrl]);


function 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]);


function 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(this));
}
//END TEST APP CODE


//WATCH UTILS
var mod = angular.module('watch_utils', []);


mod.service('apply', ['$timeout', ApplyService]);


function ApplyService($timeout) {
return function apply() {
$timeout(function() {});
};
}


mod.service('WatchObj', ['$rootScope', WatchObjService]);


function WatchObjService($rootScope) {
// target not always equals $scope, for example when using bindToController syntax in
//directives
return function watch_obj(obj, fields, target, $scope) {
// if $scope is not provided, $rootScope is used
$scope = $scope || $rootScope;
var watched = fields.map(function(field) {
return $scope.$watch(
function() {
return obj[field];
},
function(new_val) {
target[field] = new_val;
}
);
});
var unregister = function unregister_watch_obj() {
watched.map(function(unregister) {
unregister();
});
};
$scope.$on('$destroy', unregister);
return unregister;
};
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div class='test' ng-app="app" ng-controller="TestWatch">
prop1: \{\{prop1}}
<br>prop2: \{\{prop2}}
<br>prop3 (unwatched): \{\{prop3}}
<br>
<button ng-click="test1()">
Simple props change
</button>
<button ng-click="test2()">
Async props change
</button>
<button ng-click="test3()">
Async props change with apply
</button>
</div>

看看这个活塞:这是我能想到的最简单的例子?

http://jsfiddle.net/HEdJF/

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