AngularJS using $rootScope as a data store
Asked Answered
Z

5

58

I have an idea for my AngularJS app and I'm curious if the AngularJS community would consider it okay to do it this way. In short, I am connecting to a data API and displaying my results on the page.

I have created an AngularJS service that creates a data store on $rootScope.DataStore. I also have a service method that updates the DataStore with the data returned from an API endpoint. If I request the "products" API endpoint from inside my controller with DataStore.update('products'), this would update $rootScope.DataStore.products with my product data.

Now, in the view/partial, all I need to do is say ng-repeat="product in DataStore.products" to show my data, and it doesn't matter what controller scope I am in. So, in essence my DataStore is my single source of truth.

What I feel like I gain from this method is easy to follow semantics and minimal controller coding. So, anytime the DataStore is updated, anything that's bound to DataStore also gets updated.

Would this put too much load on the $rootScope digest cycle, or is this just an odd way to do it? Or is it a totally awesome way? :) Any comments are welcome.

Zebe answered 24/5, 2013 at 15:58 Comment(0)
J
90

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.

Julissa answered 24/5, 2013 at 16:11 Comment(11)
since he already has the service, and since part of its job is also to manage the API (not just to store and return), that seems to argue against using the $rootScope in this case.Nonetheless
Personally, I prefer to keep data off of $rootScope but here is the endorsement from the FAQ. So since there are no real concerns with using $rootScope this way, it comes down to programmer preference and what makes the most sense in any given app.Julissa
Thank you all for your input!Zebe
Thanks for indicates the part that says it's better to use $rootScope to store (carefully) data and not services for that purpose.Adeline
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. What? - can somebody translate?Prescript
@sir-ben-benji, which language do you need this translated to ;-)Howlett
"basic dirty checking" and "data mutation" are not terms I think are standart. Can you elaborate?Prescript
This bit confuses me as-well. I don't see how stating that digest cycle implements basic dirty checking implies that it doesn't put too much load on the digest cycle. To me that just sentence just aknowledges the fact that all variables on $rootScope are checked during digest cycle and more variables = slower cycle.Trippet
The parenthetical bit was not meant to imply anything; it's additional information. Read the sections on performance at these 2 links: blog.bguiz.com/post/57373805814/… -- #9682592.Julissa
In terms of the impact of $rootScope on the $digest cycle, I think the issue is not so clear. $digest dirty-checks all your watched expressions, not your entire scope. But then having too many variables from your $rootScope available as params for evaluating those expressions would the impact.Gulosity
@bitstrider I suggest you read Misko's post. You would need maybe thousands or ten thousands of watches to begin to experience performance problems. Javascript is fast.Julissa
H
34

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');
Hendrick answered 1/10, 2013 at 15:10 Comment(4)
Hi Breck, Awesome comment of your ! And explained to the details for all newbies.. That's service ! I'm trying it on my AngularJS self tutorial app so as to have such a cache service instead of my array in the root scope.Randazzo
Great! Well Explained!Ramonaramonda
I have just started using AngularJS and would like to know, will this cahche is preserve when user closes or reopen the app or restart his phone..Bucentaur
You would have to tie it into local storage or something like that. If you have just started angular, you should stop and use react instead ;)Hendrick
A
9

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.

Architectonics answered 18/11, 2013 at 14:57 Comment(0)
N
0

I guess I'm not sure why you need to use the rootScope? The lifetime of a service instance is the entire application as well, so whatever data schema / semantics you are using could also be stashed right in the service itself and it would be shared across controllers. Either of those methods however don't survive refresh like using local storage would.

The rest of it sounds like a lazy loading approach. Where the service is the only thing "aware" of whether the data has been loaded from the remote and returns it if it is already cached and caches and returns it if its not? If I understand that part correctly its a good pattern.

edit: Here I am taking a similar approach to the lazy loading, notice the cache is just in the service itself:

angular.module('HN_Reddit_Mashup.Services', [])
    .factory('HackerNews', function($http) {
        var HackerNewsCache = {};
        return {
            get: function(url) {
                return HackerNewsCache[url] ||
                    (HackerNewsCache[url] = $http.jsonp("http://api.thriftdb.com/api.hnsearch.com/items/_search?q=" + url +     "&callback=JSON_CALLBACK"));
            },                
        };
    })
Nonetheless answered 24/5, 2013 at 16:11 Comment(4)
My main reason for putting a data store on $rootScope is just easy access in my views/partials, and for semantics. {{DataStore.endpoint.some_data}} seems to make a lot of since to me. You know exactly where the data is coming from and you don't need to do much controller work. Just tell the service to update the data store with something like DataStore.update('endpoint');. Thanks for the comments!Zebe
Having a "service" in the Angular sense of the word is redundant then, its really just a utility function. managing rootScope access.Nonetheless
I like to keep stuff in services and expose it sparingly on individual scopes, so that I don't get "crazy" and start depending too heavily on the object model of the long-lived "stuff" within the UI. If each scope only has what's relevant to the task at hand, you are less likely to run into trouble (or worse, work) if you need to or want to refactor the "stuff." It's a stylistic choice that seems beneficial to me. Also, when the $rootScope is digested, anything assigned to it will be dirty-checked -- this could (theoretically) have a measurable performance impact.Incorporeity
In my opinion, you should avoid using the RootScope. Using RootScope will make testing very difficult. Instead, I think you should consider (as mentioned by others) to put the data inside the Service itself. This will also help you maintain the code much easier, as you have all the code in one place (the service). To me it sounds like you're too lazy injecting the service into your controllers, lol.... (just kidding)Unionist
L
0

I am just facing the same issue, and it seems to me that storing it ion globally available 'location' is the right approach, but that $rootScope is not the ideal place.

I have just been researching this more, and instead of storing your data on $rootScope, you could consider using a "service" to manage your data / separate concerns, as described here (especially the last code example): http://joelhooks.com/blog/2013/04/24/modeling-data-and-state-in-your-angularjs-application/

Then in the "service" you create using that approach, whether you save the data in memory, cacheFactory, localstorage (as alluded to here), and/or to your DB (eg. via AJAX), is up to whatever suits the needs of your app. It also means changes to how you store your data can be made independently, as required.

Lamberto answered 11/10, 2013 at 3:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.