AngularJS:如何看服务变量?

我有一个服务,说:

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被更新的时间,我已经把这个模式拼凑在了一起,在这个模式里我添加一个服务到控制器的$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; } }); } 

这感觉是长期的,我已经在使用服务变量的每个控制器中重复它。 有没有更好的方法来完成观看共享变量?

如果你想避免$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来更改项目。

而不是创建自己的监听器注册表(必须清理各种$破坏),你应该能够从有问题的服务$broadcast

您仍然必须在每个监听器$on编写$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()函数完成且数据有效时,上面的服务将向每个范围广播消息。 或者,如果是$资源或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 .. }); }]); 

其中一个好处是消除了多个手表。 如果你是像上面的例子那样组合字段或派生值,你必须同时观察名字和姓氏属性。 看到getUser()函数只有在用户对象在更新时被替换才会起作用,如果用户对象只是更新其属性,则不会触发。 在这种情况下,你必须做深入的观察,这是更密集的。

$ broadcast将消息从调用的范围发送到任何子范围。 所以从$ rootScope调用它将会触发每个范围。 例如,如果您要从控制器的作用域进行广播,则只会在继承自控制器作用域的作用域中触发。 $ emit的方向相反,其行为类似于DOM事件,因为它激发了范围链。

请记住,有一些情况下,$广播很有意义,有些情况下,$ 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; }) 

那么前两个参数是成功和错误回调,第三个是通知回调。

参考$ q。

没有手表或观察者回调( 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'将引用窗口对象。

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

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

如果在这个相同的Ctrl中的其他变量依赖于foo改变,那么是的,你需要观察foo并对该变量进行修改。 但只要它是一个简单的参考观看是不必要的。 希望这可以帮助。

我偶然发现了一个类似的问题,但是我认为这个问题应该是一个彻底的解释,以及我的解决方案。

当你使用的角度表达式出现在HTML中时,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> 

你可以在$ 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 }); }]); 

用这种方式,你把所有相关的工厂代码放在其描述中,那么你只能依靠外面的广播

== ==修订

现在在$手表很简单。

笔在这里 。

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']; 

虽然面对一个非常类似的问题,我看了一个函数的范围,并有函数返回服务变量。 我创建了一个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++; }); }); 

I came to this question but it turned out my problem was that I was using setInterval when I should have been using the angular $interval provider. This is also the case for setTimeout (use $timeout instead). I know it's not the answer to the OP's question, but it might help some, as it helped me.

I have found a really great solution on the other thread with a similar problem but totally different approach. Source: AngularJS : $watch within directive is not working when $rootScope value is changed

Basically the solution there tells NOT TO use $watch as it is very heavy solution. Instead they propose to use $emit and $on .

My problem was to watch a variable in my service and react in directive . And with the above method it very easy!

My module/service example:

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

So basically I watch my user – whenever it is set to new value I $emit a user:change status.

Now in my case, in the directive I used:

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

Now in the directive I listen on the $rootScope and on the given change – I react respectively. Very easy and elegant!

For those like me just looking for a simple solution, this does almost exactly what you expect from using normal $watch in controllers. The only difference is, that it evaluates the string in it's javascript context and not on a specific scope. You'll have to inject $rootScope into your service, although it is only used to hook into the digest cycles properly.

 function watch(target, callback, deep) { $rootScope.$watch(function () {return eval(target);}, callback, deep); }; 

// service: (nothing special here)

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

A wee bit ugly, but I've added registration of scope variables to my service for a toggle:

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

And then in the controller:

 function MyCtrl($scope, myService) { $scope.name = 'Superhero'; $scope.myVar = false; myService.register($scope, 'myVar'); } 

I've seen some terrible observer patterns here that cause memory leaks on large applications.

I might be a little late but it's as simple as this.

The watch function watches for reference changes (primitive types) if you want to watch something like array push simply use:

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

This will update the reference and update the watch from anywhere. Including a services getter method. Anything that's a primitive will be updated automatically.

I have written two simple utility services that help me track service properties changes.

If you want to skip the long explanation, you can go strait to 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; }; } 

Have a look at this plunker:: this is the simplest example i could think of

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

I am late to the part but I found a nicer way to do this than the answer posted above. Instead of assigning a variable to hold the value of the service variable, I created a function attached to the scope, that returns the service variable.

调节器

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

I think this will do what you want. My controller keeps checking the value of my service with this implementation. Honestly, this is much simpler than the selected answer.

Interesting Posts