AngularJS : How to watch service variables?
I have a service, say:
factory('aService', ['$rootScope', '$resource', function ($rootScope, $resource) {
var service = {
foo: []
};
return service;
}]);
And I would like to use foo
to control a list that is rendered in HTML:
<div ng-controller="FooCtrl">
<div ng-repeat="item in foo">{{ item }}</div>
</div>
In order for the controller to detect when aService.foo
is updated I have cobbled together this pattern where I add aService to the controller's $scope
and then use $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;
}
});
}
This feels long-handed, and I've been repeating it in every controller that uses the service's variables. Is there a better way to accomplish watching shared variables?
You can always use the good old observer pattern if you want to avoid the tyranny and overhead of $watch
.
In the service:
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();
});
});
And in the controller:
function FooCtrl($scope, aService){
var updateFoo = function(){
$scope.foo = aService.foo;
};
aService.registerObserverCallback(updateFoo);
//service now in control of updating foo
};
In a scenario like this, where multiple/unkown objects might be interested in changes, use $rootScope.$broadcast
from the item being changed.
Rather than creating your own registry of listeners (which have to be cleaned up on various $destroys), you should be able to $broadcast
from the service in question.
You must still code the $on
handlers in each listener but the pattern is decoupled from multiple calls to $digest
and thus avoids the risk of long-running watchers.
This way, also, listeners can come and go from the DOM and/or different child scopes without the service changing its behavior.
** update: examples **
Broadcasts would make the most sense in "global" services that could impact countless other things in your app. A good example is a User service where there are a number of events that could take place such as login, logout, update, idle, etc. I believe this is where broadcasts make the most sense because any scope can listen for an event, without even injecting the service, and it doesn't need to evaluate any expressions or cache results to inspect for changes. It just fires and forgets (so make sure it's a fire-and-forget notification, not something that requires action)
.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;
}]);
The service above would broadcast a message to every scope when the save() function completed and the data was valid. Alternatively, if it's a $resource or an ajax submission, move the broadcast call into the callback so it fires when the server has responded. Broadcasts suit that pattern particularly well because every listener just waits for the event without the need to inspect the scope on every single $digest. The listener would look like:
.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 ..
});
}]);
One of the benefits of this is the elimination of multiple watches. If you were combining fields or deriving values like the example above, you'd have to watch both the firstname and lastname properties. Watching the getUser() function would only work if the user object was replaced on updates, it would not fire if the user object merely had its properties updated. In which case you'd have to do a deep watch and that is more intensive.
$broadcast sends the message from the scope it's called on down into any child scopes. So calling it from $rootScope will fire on every scope. If you were to $broadcast from your controller's scope, for example, it would fire only in the scopes that inherit from your controller scope. $emit goes the opposite direction and behaves similarly to a DOM event in that it bubbles up the scope chain.
Keep in mind that there are scenarios where $broadcast makes a lot of sense, and there are scenarios where $watch is a better option - especially if in an isolate scope with a very specific watch expression.
I'm using similar approach as @dtheodot but using angular promise instead of passing callbacks
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);
}
})
Then wherever just use myService.setFoo(foo)
method to update foo
on service. In your controller you can use it as:
myService.observeFoo().then(null, null, function(foo){
$scope.foo = foo;
})
First two arguments of then
are success and error callbacks, third one is notify callback.
Reference for $q.
链接地址: http://www.djcxy.com/p/77852.html上一篇: AngularJS控制器能否继承同一模块中的另一个控制器?
下一篇: AngularJS:如何看服务变量?