AngularJS : How to watch service variables?
我有一项服务,比如:
1 2 3 4 5 6 7 | factory('aService', ['$rootScope', '$resource', function ($rootScope, $resource) { var service = { foo: [] }; return service; }]); |
我想用
1 | {{ item }} |
为了让控制器检测何时更新
1 2 3 4 5 6 7 8 9 10 | function FooCtrl($scope, aService) { $scope.aService = aService; $scope.foo = aService.foo; $scope.$watch('aService.foo', function (newVal, oldVal, scope) { if(newVal) { scope.foo = newVal; } }); } |
这让人觉得很费劲,我在每个使用服务变量的控制器中都重复了这一点。有没有更好的方法来完成监视共享变量?
如果您想避免
在服务中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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(); }); }); |
在控制器中:
1 2 3 4 5 6 7 8 | function FooCtrl($scope, aService){ var updateFoo = function(){ $scope.foo = aService.foo; }; aService.registerObserverCallback(updateFoo); //service now in control of updating foo }; |
在这样一个场景中,如果多个/未知对象可能对更改感兴趣,请使用要更改的项中的
与其创建自己的侦听器注册表(必须在各种$destroys上清除),还不如从相关服务中删除cx1(3)。
您仍然必须在每个监听器中对
通过这种方式,监听器也可以从DOM和/或不同的子作用域来来回,而不需要服务改变其行为。
**更新:示例**
广播在"全球"服务中最有意义,它可能影响到应用程序中无数其他东西。一个很好的例子是用户服务,其中可能会发生许多事件,如登录、注销、更新、空闲等。我认为这是广播最有意义的地方,因为任何范围都可以监听事件,甚至不需要注入服务,而且不需要评估任何表达式或缓存结果来检查ch安吉斯。它只会触发并忘记(所以要确保它是一个"触发并忘记"通知,而不是需要动作的东西)
1 2 3 4 5 6 7 8 9 10 11 12 13 | .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上的作用域。听者看起来像:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | .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的方法,但使用角度承诺而不是通过回调
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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); } }) |
然后,只要使用
1 2 3 | myService.observeFoo().then(null, null, function(foo){ $scope.foo = foo; }) |
参考$Q.
没有手表或观察员回拨(http://jsfiddle.net/zymotik/853wvv7s/):
JavaScript:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | 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
1 2 3 4 | <h4>{{service.name}}</h4> <p> Count: {{service.count}} </p> |
当我们从服务传递一个对象而不是一个值时,这个javascript工作。当JavaScript对象从服务返回时,Angular会将监视添加到其所有属性中。
另外请注意,我使用的是"var self=this",因为在$timeout执行时需要保留对原始对象的引用,否则"this"将引用窗口对象。
我偶然发现这个问题是为了寻找类似的东西,但我认为它值得对正在发生的事情进行彻底的解释,以及一些额外的解决方案。好的。
当一个角度表达式(如您使用的表达式)出现在HTML中时,angular会自动为
1 | {{ item }} |
这里未声明的问题是,有两件事之一正在影响
问题1:过时的参考资料
考虑到第一种可能性,假设应用了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | 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; } ]); |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | <!DOCTYPE html> <html> <head> <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"> <link rel="stylesheet" href="style.css" /> <script src="script.js"> </head> <body ng-app="myApp"> Array changes on each update {{ item }} Array is the same on each udpate {{ item }} </body> </html> |
好的。
如您所见,当
然而,虽然有几种方法可以确保初始引用保持一致,但有时可能需要更改对象或数组。或者,如果服务属性引用像
前面给出的几个答案已经给出了该问题的一些解决方案。但是,我个人赞成使用Jin和The Allweeks在评论中建议的简单方法:好的。
just reference aService.foo in the html markup
Ok.
解决方案1-b:将服务附加到作用域,并在HTML中引用
意思是,只要这样做:好的。
HTML:好的。
1 | {{ item }} |
JS:好的。
1 2 3 | function FooCtrl($scope, aService) { $scope.aService = aService; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | 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; } ]); |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | <!DOCTYPE html> <html> <head> <script data-require="[email protected]" data-semver="1.4.7" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.7/angular.js"> <link rel="stylesheet" href="style.css" /> <script src="script.js"> </head> <body ng-app="myApp"> Array changes on each update {{ item }} </body> </html> |
好的。
这样,
这正是你试图用你的工作区来做的,但在方法上却没有那么圆滑。您在控制器中添加了一个不必要的
现在,假设应用了
这里的许多解决方案都可以解决这个问题,但我同意代码窃听器:好的。
The reason why we're using a framework like Angular is to not cook up our own observer patterns
Ok.
因此,我宁愿继续在HTML标记中使用
我很惊讶还没有人建议使用二传手和getter。这种能力是在ecmascript5中引入的,因此已经存在多年了。当然,这意味着,无论出于什么原因,如果您需要支持真正的老浏览器,那么这个方法将不起作用,但我觉得getter和setter在javascript中的使用非常少。在这种特殊情况下,它们可能非常有用:好的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | factory('aService', [ '$rootScope', function($rootScope) { var realFoo = []; var service = { set foo(a) { realFoo = a; $rootScope.$apply(); }, get foo() { return realFoo; } }; // ... } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | 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; } ]); |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | <!DOCTYPE html> <html> <head> <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"> <link rel="stylesheet" href="style.css" /> <script src="script.js"> </head> <body ng-app="myApp"> Using a Getter/Setter {{ item }} </body> </html> |
好的。
在这里,我在服务函数中添加了一个"private"变量:
注意在set函数中使用
如果
据我所知,你不必做那么复杂的事情。您已经将foo从服务分配给您的作用域,因为foo是一个数组(而对象又是通过引用分配的!)所以,你需要做的就是这样:
1 2 3 4 | function FooCtrl($scope, aService) { $scope.foo = aService.foo; } |
如果同一个ctrl中的某个变量取决于foo的变化,那么是的,您需要一个手表来观察foo并对该变量进行更改。但只要是一个简单的参考观察是不必要的。希望这有帮助。
您可以将服务插入$rootscope并观察:
1 2 3 4 5 6 | myApp.run(function($rootScope, aService){ $rootScope.aService = aService; $rootScope.$watch('aService', function(){ alert('Watch'); }, true); }); |
在控制器中:
1 2 3 | 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+
可以观察一个、多个或所有对象属性的更改。
例子:
1 2 3 4 5 6 7 8 9 | 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");? |
您可以观看工厂内部的更改,然后广播更改
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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; }); |
然后在控制器中:
1 2 3 4 5 6 | angular.module('MyApp').controller('aController', ['$rootScope', function ($rootScope) { $rootScope.$on('aFactory:keyChanged', function currentCityChanged(event, value) { // do something }); }]); |
通过这种方式,您将所有相关的工厂代码都放在其描述中,那么您只能依靠外部广播。
=更新=
非常简单,现在在$watch。
钢笔在这里。
HTML:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | <p> FooController </p> <p> Send one item </p> <p> Send two items </p> <p> Send three items </p> <p> Send name: Sheldon </p> <p> Send name: Leonard </p> <p> Send name: Penny </p> <p> BarController </p> <p ng-if="name">Name is: {{ name }} </p> {{ item.name }} |
JavaScript:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | 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 + '!'); }); }]); |
根据气味的回答,您可以使用类似于下面的内容,以确保您不会忘记注销回调…但有些人可能反对将
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | 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是如下扩展方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | /** * 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; }; |
这是我的一般方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | 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'); } }]); |
In Your Controller
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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注入到您的服务中,尽管它只用于正确地钩住摘要循环。
1 2 3 | function watch(target, callback, deep) { $rootScope.$watch(function () {return eval(target);}, callback, deep); }; |
我来问这个问题,但结果发现我的问题是,当我应该使用angular$interval提供程序时,我使用的是setinterval。这也是setTimeout的情况(改用$timeout)。我知道这不是OP问题的答案,但它可能会帮助一些人,因为它帮助了我。
我在另一个线程上发现了一个非常好的解决方案,它有一个相似的问题,但方法完全不同。源:当$rootscope值更改时,angularjs:$watch within指令不起作用
基本上,这里的解决方案告诉不要使用
我的问题是观察我的服务中的一个变量并在指令中作出反应。用上面的方法很容易!
我的模块/服务示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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; }, ... }; }); |
所以基本上,我观察我的
现在在我的例子中,在我使用的指令中:
1 2 3 4 5 6 7 8 9 | angular.module('xxx').directive('directive', function (Auth, $rootScope) { return { ... link: function (scope, element, attrs) { ... $rootScope.$on('user:change', update); } }; }); |
现在,在指令中,我分别听取了
当面对一个非常相似的问题时,我观察了作用域中的一个函数,并让该函数返回服务变量。我创造了一把JS小提琴。您可以找到下面的代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | 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++; }); }); |
看一下这个破坏者:这是我能想到的最简单的例子。
http://jsfiddle.net/hedjff/
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | <input type="text" ng-model="Data.FirstName"><!-- Input entered here --> Input is : {{Data.FirstName}}<!-- Successfully updates here --> Input should also be here: {{Data.FirstName}}<!-- How do I automatically updated it here? --> // 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; }); |
//服务:(这里没什么特别的)
1 2 3 | myApp.service('myService', function() { return { someVariable:'abc123' }; }); |
//CTRL:
1 2 3 4 5 6 7 8 9 10 11 | 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; }); }); |
有点难看,但我已经在我的服务中添加了范围变量注册,以便切换:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | 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; }); |
然后在控制器中:
1 2 3 4 5 | function MyCtrl($scope, myService) { $scope.name = 'Superhero'; $scope.myVar = false; myService.register($scope, 'myVar'); } |
我在这里看到了一些可怕的观察者模式,它们会导致大型应用程序内存泄漏。
我可能有点晚了,但就这么简单。
watch函数监视引用更改(基元类型),如果要监视类似array push的内容,只需使用:
这将更新引用并从任何地方更新手表。包括服务getter方法。任何原语都将自动更新。
我迟到了,但我找到了比上面的答案更好的方法。我没有指定一个变量来保存服务变量的值,而是创建了一个附加到作用域的函数,该函数返回服务变量。
控制器
1 2 3 | $scope.foo = function(){ return aService.foo; } |
我想这会满足你的需要。我的控制器一直用这个实现检查我的服务的值。老实说,这比选择的答案简单得多。
我编写了两个简单的实用程序服务,帮助我跟踪服务属性的更改。
如果你想略过冗长的解释,你可以直接去jfiddle。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | 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; }; } |
此服务在控制器中的使用方式如下:假设您有一个具有属性"prop1"、"prop2"、"prop3"的服务"testservice"。要监视并分配到范围"prop1"和"prop2"。有了手表服务,看起来会是这样的:
1 2 3 4 5 6 7 8 | 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); } |
1 2 3 4 5 6 7 | mod.service('apply', ['$timeout', ApplyService]); function ApplyService($timeout) { return function apply() { $timeout(function() {}); }; } |
我将在异步代码的末尾触发它来触发$digest循环。像这样:
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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)); } |
所以,所有这些看起来都是这样的(你可以运行它或者打开小提琴):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 | // 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; }; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"> <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"> prop1: {{prop1}} prop2: {{prop2}} prop3 (unwatched): {{prop3}} <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> |