I have faced something similar in the past, and considered four possible approaches:
- Using
$broadcast
- Storing settings on
$rootScope
- Observer pattern (as you have here)
- Using
$watch
from a controller
Here are my thoughts on each:
$broadcast
In an AngularJS presentation I saw, Miško Hevery spoke about the use of $broadcast
(i.e. events) and the use cases for such. The gist was that $broadcast
is more intended for reacting to events that are not closely coupled with whatever you are working with, otherwise an alternative is likely preferable. Also on this subject, the Best Practices guide on the angular wiki recommends that:
Only use .$broadcast(), .$emit() and .$on() for atomic events: Events that are relevant globally across the entire app (such as a user authenticating or the app closing).
Here, as you have settings which are closely associated to whatever populates ng-view
, it would suggest an alternative to using $broadcast
is preferable.
$rootScope
This is a global state as you mention (and want to avoid). It wasn't/isn't my personal preference either to expose settings to my entire app, despite it often being the easy option. I personally reserve $rootScope
for configuration settings and 'soft' variables, like page title etc. I wouldn't elect to use this option.
Observer Pattern
Registering callbacks against the Configuration factory is a solid approach. In regard to your persistent callbacks, you can listen for the $destroy
event on the scope, calling a remove
method on your Configuration factory to remove the callback. This could be considered a good example of how $broadcast
be used; the controller is concerned with the event and must react to it, but the event itself is not specific to the data shared by the controllers/Configuration service.
$watch
By using a shared service, it can be injected it into any controller concerned with the settings. Right now, any change to the config will trigger your callback, when perhaps some views may only be concerned with one or two configuration settings. $watch
will allow you to easier observe changes to only those attributes. I can't speak to the overhead vs registering callbacks, but this feels like the most 'angular' way to me.
This is how this could be implemented using $watch
:
var app = angular.module("myApp",[]);
app.factory("Configuration",function(){
var data = {
settingOne: true,
settingTwo: false
};
return data;
})
app.controller("SettingsCtrl",function($scope, Configuration){
// do something
})
app.controller("HomeCtrl",function($scope, Configuration){
// detect any change to configuration settings
$scope.$watch(function() {
return Configuration;
}, function(data) {
// do something
}, true)
// alternatively only react to settingTwo changing
$scope.$watch(function() {
return Configuration.settingTwo
}, function(data) {
// do something
})
})
Note that if you were to require a slightly more complicated Configuration factory, you could shift to using getter/setter methods and keep the config settings themselves private. Then, in the $watch
, you should watch the method call instead of the property itself.
UPDATE:
At the time of answering, I preferred the approach of a $watch
within a controller. After some time developing with the framework, I now try to keep $watch
out of the controller altogether, instead preferring, where possible, to directly invoke a function at the point of change of the value, or through leveraging ng-change
.
One reason for such is the complexity it adds to testing the controller, but perhaps moreso that it's inefficient: for every $digest
cycle angular invokes, every registered $watch
will be evaluated regardless, and it may very well be responding to a change made to a value with an existing $watch
.
Rather than surmize the cons and solutions on this perspective, there is a very good article on exactly this issue here: Angular JS - you probably shouldn't use $watch in your controllers.