2

In my AngularJS application, I have a Session service object that contains stuff like the current user, their preferences, the current company they belong to, and the current theme that they are using. Many places in my application refer to the Session service when they need to get at this data.

Because these variables are in a service, I cannot use scope watches to detect changes. So instead, I've decided to use the observer pattern. Various places in the application, such as other services, directives, controllers, etc. will register themselves with the Session service and provide a callback to be executed whenever the Session changes.

For example, if the user changes their theme, the <style> element in index.html that uses a custom directive will be notified, and it will recreate all of the overriding css rules for the new colors.

For another example, whenever the user's avatar is updated, the main menu bar controller will be notified to refresh and redraw the avatar. Stuff like this.

Obviously the data in Session has to be refreshed at least once before the various controllers, directives, etc. use it. The natural place to ask the Session service to get its session data was in a run block for the application-level module. This works pretty well, but I don't think it's the best place either.

One problem I have noticed is that when Firebug is open, the asynchronous nature of things loading causes ordering issues. For example, the directive that runs on the <style> element will run AFTER the Session service has refreshed in the application's run block... which means the theme will not get updated after pressing F5 because the callback is registered after the initialization of the data occured. I would have to call a manual refresh here to keep it in sync, but if I did that, it may execute twice in the times where the order is different! This is a big problem. I don't think this issue is just related to Firebug... it could happen under any circumstance, but Firebug seems to cause it somewhat consistently, and this is bad.

To recap... This asynchronous ordering is good:

  1. Theme Directive registers callback to Session
  2. Menu Bar application controller registers callback to Session
  3. Session.refresh() is called in .run block.

This asynchronous ordering is bad:

  1. Menu Bar application controller registers callback to Session
  2. Session.refresh() is called in .run block.
  3. Theme Directive registers callback to Session, but callback does not get executed since Session.refresh() was already executed.

So rather than use the observer pattern, or refresh the Session state via a run block, what the best way to design the services, etc. so that the session data will ALWAYS get refreshed after (or maybe before) the various other parts of the application require it? Is there some kind of event I can hook into that gets executed before directives and controllers are executed instead of the run block?

If my approach is generally sound, what can I add to it to really make it work the way it should?

Thanks!

egervari
  • 22,372
  • 32
  • 121
  • 175
  • Does this session data load from server? – Chandermani Feb 27 '14 at 05:09
  • Of course :) It makes an http get on /session, getting all of these items. It does this in a run block currently, but I am open to other suggestions. Whenever the app needs the session to refresh again, it can invoke the same method manually. – egervari Feb 27 '14 at 05:19
  • In general, Angular's whole deal is to access the data directly and let the internal `$digest` handle updating other data. Can your modules just access the Session variables themselves? – willoller Feb 27 '14 at 05:25
  • Well, if I did that, the application might send 4-5 requests for session data in different spots, maybe more as I need them, rather than just make 1 request for all of the session data. Isn't getting it all in 1 chunk better? – egervari Feb 27 '14 at 05:31
  • Sure, you can update the data in one chunk - get it from the db, store it in the session service - but then you let Angular figure out which bindings need to update using its internal dirty checking. Way more efficient, way less code for you. – willoller Feb 27 '14 at 05:36
  • Take a look at parent controller http://stackoverflow.com/a/21488757/356380 – Whisher Feb 27 '14 at 09:28

2 Answers2

1

In angular.js you have 2 way of using global variables:

  1. use a $rootScope
  2. use a service

Using $rootScope is very easy as you can simply inject it into any controller and change values in this scope. All global variables have problems! Services is a singletons(What you need)!

I think in your case you can use

$rootScope

And

$scope.$watch

Great answer

Community
  • 1
  • 1
Farkhat Mikhalko
  • 3,565
  • 3
  • 23
  • 37
  • Isn't using the rootScope generally considered bad practice? – egervari Feb 27 '14 at 05:32
  • The easiest way i think, not bad. – Farkhat Mikhalko Feb 27 '14 at 05:34
  • I had it working this way at an earlier time, but I thought maybe it was bad practice because something else could conflict with it. If some other thing overwrites `$rootScope.currentTheme`, wouldn't that be bad? Isn't it better to protect it behind a service? – egervari Feb 27 '14 at 06:10
  • You can use define_getter for user object, and put this object into $rootScope – Farkhat Mikhalko Feb 27 '14 at 06:11
  • Oh, I just realized... you can watch service functions?! I thought they had to be variables on the scope itself in order to watch them. That just might be the answer. – egervari Feb 27 '14 at 06:12
  • @egervari will be great, if my answer helps to you )))) – Farkhat Mikhalko Feb 27 '14 at 06:13
  • Yeah, given the complex stuff I'm doing with the server as well as having to call a lot of client side logic when the session data changes, having the watches makes things work the easiest. Since I can watch the function for changes, it doesn't use the $rootScope, so I think this approach should be okay. Even if it's not as efficient, it won't get updated very often. I just want things to work 100% of the time and I want it to be simple, and I think this will be the best approach. Thanks! – egervari Feb 27 '14 at 06:29
  • @egervari can you give your email, or write to me farhatmihalko@gmail.com, because in next two months i will start to build big app in angular.js – Farkhat Mikhalko Feb 27 '14 at 06:34
0

Is there a reason you can't access the variables directly like this:

app.factory('SessionService', function() {
    var items = {
        "avatar": "some url"
    };
    return items;
});

var MainController = [$scope, 'SessionService', function($scope, SessionService){
    $scope.session = SessionService;

    $scope.modifyAvatar = function(url){
        $scope.session.avatar = "some new url";
    };
}];

var HeaderController = [$scope, 'SessionService', function($scope, SessionService){
    $scope.session = SessionService;

    // You probably wouldn't do this, you would just bind
    // to {{session.avatar}} in your template
    $scope.getAvatar = function(){
        return $scope.session.avatar;
    };
}];
willoller
  • 7,106
  • 1
  • 35
  • 63
  • The reason why I am using the observer pattern is that some values are actually being added/calculated on the server, not the client. So in some cases, it may be possible to update the session locally at the same time I am pushing it to the server, however much of the time I need to call `/session` to get the current state as the server sees it, because otherwise I am just duplicating logic on the server... or I can't even get at the new data because there is no good way for the client to figure it out. It's server-side business-layer stuff. – egervari Feb 27 '14 at 06:07
  • In that case, your SessionService (or other appropriate service) should be the intermediary between the client and the server. Put the logic for getting the server data in there, and give a public method for overriding the cache and retrieving the data from the server. That way, the Service could call the db whenever it wants and your other Services or Controllers could ask the SessionService to refresh from the database. – willoller Feb 27 '14 at 06:11
  • you don't need to watch the function - you call the function and bind the result. that way you don't have to watch anything, because the bindings get updated. – willoller Feb 27 '14 at 06:12