AngularJS using $rootScope as a data store

Solution 1:

This question is addressed in the AngularJS FAQ quoted here:

Occasionally there are pieces of data that you want to make global to the whole app. For these, you can inject $rootScope and set values on it like any other scope. Since the scopes inherit from the root scope, these values will be available to the expressions attached to directives like ng-show just like values on your local $scope.

It seems that the team does encourage using $rootScope this way, with this caveat:

Of course, global state sucks and you should use $rootScope sparingly, like you would (hopefully) use with global variables in any language. In particular, don't use it for code, only data. If you're tempted to put a function on $rootScope, it's almost always better to put it in a service that can be injected where it's needed, and more easily tested.

Conversely, don't create a service whose only purpose in life is to store and return bits of data.

This does not put too much load on the $digest cycle (which implements basic dirty checking to test for data mutation) and this is not an odd way to do things.

EDIT: For more details on performance, see this answer from Misko (AngularJS dev) here on SO: How does data binding work in AngularJS? Specifically note the section on performance.

Solution 2:

To appease all parties, why not just use the $cacheFactory. This allows the data request services to be stateless and basically just a getter and setter. I will admit keeping the data on the $rootScope or as a property in the service is convenient but just feels wrong. Using the $cacheFactory is pretty easy too.

First create a cache service:

angular.module('CacheService', ['ng'])
    .factory('CacheService', function($cacheFactory) {
    return $cacheFactory('CacheService');
});

Include the js file in in your app.js and then inject it into you app declaration:

var MyApp = angular.module('MyApp', ['CacheService']);

Inject it in the service, use it like so:

'use strict'

MyApp.factory('HackerNewsService', function(CacheService) {
    return {
        getNews: function(key) {
            var news = CacheService.get(key);

            if(news) {
                return news;
            }

            return null;
        },
        setNews: function(key, value) {
            CacheService.put(key, value);
        },
        clearNews: function(key) {
            CacheService.put(key, '');
        }
    };
});

Now all that you have to do is inject your HackerNewsService in your controller and use it by calling the methods we created on it. For example:

HackerNewsService.setNews('myArticle', {headline: 'My Article', body: 'This is the body'});
$scope.article = HackerNewsService.getNews('myArticle');

Solution 3:

My experience is that using the $rootScope for storing the part of my datamodel that is common to all ngViews in my app, is the most convenient way.

<div>{{mymodel.property}}</div>

is for me more readable and shorter than

<div>{{getPropertyModel()}}</div>

with the javascript

app.factory('MyModel', function(){
    return {
        getModel: function() { ... },
        setModel: function(m) { ... },
    }
});

app.controller('ctrl', ['$scope', 'MyModel', function($scope, MyModel){
    $scope.getPropertModel = function() {
        return MyModel.getModel().property;
    };
}]);

If one uses a service or a cachefactory, every acces to the model in the html template becomes a function, which is less readable that a accessing a property of the rootScope. Using the $rootScope gives less code, and as a consequence less errors and less testing.

Of course only the common part of all ngView's is stored in $rootScope. The rest of the model is stored in the local $scope.

Watches on a function are also slower than on object properties. So performance-wise, $rootScope is also better.