Sharing Data, State and Models on AngularJS: Alternatives, comparison and my solution

posted in: AngularJS | 9

Once you start using AngularJS for all of your apps, you’ll see that each time your apps are bigger and they use more JS and Angular. At this point, the regular example of TODO list isn’t really useful anymore. At this point we really need to start sharing data and state. In other words, we need to have models that are known by several controllers and directives and we need to be able to be notified of this model changes.

Once we realize this, we’ll see that there’re actually a bunch of options for doing so, and at first we don’t really know which is the best one. I’ve personally tried all of them, and have had to refactored the code sometimes as I ended up getting the unfourtenately-too-well-known spaghetti code.

Let’s first state all of the options:

1) We can use Models as a service. What does this mean? We create a service that will actually be a model. This “service” will have state and data. So, each time we need to use this model, we just import it in the controller or in the directive and we use it to set or get values. But what about being notified? We can actually assign this “service” to a scope variable in the first line of our controller and then use the $watch method from the scope to see if it has changed. For example:

2) Using ng-include. Using ng-include, we can include some other piece of HTML. This HTML will have a new scope (ng-include does this) and will create a controller for it. So, we’ll have a Controller inheritance with ng-include. $scope is inherited as well (It uses prototypal inheritance), so when you call something in the child controller’s scope, if it’s not there, it’ll go look to the parent. We can get notified of changes using $watch as well.

3) Using NOT isolated directives. This is very similar to the option #2. We can create some directives that will inherit scope from parent and share model like this. Remember that each directive can have a Controller. In this case Scope won’t be isolated.

4) Using isolated directives. We can use directives with an isolated scope (declaring scope: {stuff: ‘=’} for example) so that we can actually reuse them easier. This directive don’t depend on the parent scope. They depend on getting some parameters from the parent. Those paramters are stated in the scope configuration of the directive declaration. In the example 2 lines above, we expect to get an attribute named stuff. The value will be name of the model from the parent to which we’ll have a bidirectional asociation. In this case, we can use $watch to get notified as well

5) Using events. We can use events to comunicate between controllers, directives, etc. This events can carry extra information like a model. So we can say something like Greet Changed and sending in the event payload the new greeting. This way, all that want to know about it just gotta call the $on method from the scope stating the method name.

Let’s compare them and find their advantages and disadvantages.

As you can see, there’re a lot of options. Some of them are very similar, but have one or 2 differences.

I’ve started using events at first to decouple everything to the maximum. What’s the problem of this? That as we need to send this models or changes to model through events, we cannot rely or use the full advantages of the binding (bidirectional asociations) for models in scope. What does this mean? That I need to receive the event and manually set the value to some local scope variable. Then, the scope will taken as changed and then the DOM will change. This way, I need to send events receive them and a lot of boilerplate.

If we use option #2 or #3, as the scope variables are inherited, if we change in the child scope something in the model that was inherited, then the DOM from the parent controller (and the $watch functions) will get notified inmediately and the change will be done. This means that we don’t need to pass any value around. We just change this value where it needs to be changed and automatically this will be shown in the DOM, even if the HTML for what was changed actually “belongs” to the parent controller. But what is the disadvantage of this? Our directives and controllers are coupled with their parents. If the parent isn’t there, it doesn’t work.

So, at this point, we start thinking about #3 having isolated directives. But if a directive needs a lot of things from the parent, setting all this attributes in the directive declaration is a pain in the ass. And if we need some new attribute it’s not “magically inherited”, but we need to set it in the attribute and also define it in the scope definition from Directive.

Now, you’re thinking OK, Services models are the solution then. Well, I don’t really like it. You gotta set them to a scope variable at the first line if you want to get notified of stuff using $watch. If you have this service at multiple controllers in a “controller chain” of inehritance, you’d have this scope variable repeated all over the place in the scope. It might eventually make your app much slower, even more if this model is huge as it’s going to be in several scopes. But the good thing is that here, when we have a model dependency, it’s not implicit as with options #2 and #3, but it’s explicit, so it’s much easier to understand once it’s read.

So, what’s the best? I don’t really know. I can tell you what I’ve chosen to use.

Right now, I’m using #3 and #4. I like directives more than ng-include as they’re much more declarative and the HTML is very clear. ng-include and NOT isolated directive are VERY similar. Also another cool thing about directives is that I can transclude them.

So what I do is, I have always one parent controller that has all the important variables and methods for the whole page (for example if it has a lot of tabs). Then, I have one controller per tab (the one changed with the Route). Then, I try to divide most of the things in Directives. If the thing I’m coding is reusable in some other stuff, I use an isolated directive. When it’s isolated, the “dependencies” are explicit and it’s much easier to reuse. If I don’t want to reuse the directive, but I just use it to organize code and also need a lot of variables from the parent, then I use a NOT isolated directive. This approach has worked for me VERY well.

 

So, it’s time for u guys to try. Tell me which option you like the more, which one you’re using and why :).

Happy coding!!

Share!FacebookTwitterRedditGoogle+LinkedInbufferEmailflattr
  • http://www.raducugut.com/ Radu Cugut

    I’ve been struggling with the same questions/issues, but I never made a list of all the options like you did. Very good description, thanks! I realized I’ve chosen the same strategy as you did: one ‘master’ controller per page and then other smaller ones and then directives. But, probably because of the needs of my app, I’m using a lot of #5 – communication via events.
    Again, thanks for the insight!

  • Kevin

    What about using $rootScope in a service to broadcast global scope changes instead of depending on scope inheritance?

    I am still searching for the perfect method myself but I would be interested to hear your opinion on the method above, outlined here:
    http://joelhooks.com/blog/2013/04/24/modeling-data-and-state-in-your-angularjs-application/

    ….and probably more up your alley would be what the angular ui team is doing with routing:
    https://github.com/angular-ui/ui-router/wiki

    I’m interested to hear what your take is on routing to help define state as that can allow you to reuse directives on isolate scope.

    Great job on Restangular btw!

    • mgonto

      Hey,

      So, let’s go step by step :).

      Using $rootScope in a service to broadcast a change is almost the same as in #5 Using events. You’re going to be using some event that’s sent from the rootScope to everyone. $emit sends to the parents and $broadcast to the children.

      Events don’t leverage on Angular’s binding, so you gotta send a notification that has changed something, and then assign it to local scope, and finally get this binding.

      If by broadcast, you mean just changing a property in rootScope instead of depending in scope inheritance, that’s actually false. All scopes inherit from rootScope, so you’re actually leveraging scope inheritance this way as well. The different is that with a “main” or a father controller per page (what I do) you’re 100% sure you control where this scope is being inherited. With $rootScope you don’t control anything, it’s the parent of every scope eventually. I like it more working with familiar and well known enviroments. Also using $rootScope is even more implicit than using a MainCtrl for all the page, that’s why I’ve chosen a MainCtrl for the whole page.

      If you use AngularUI routing, you do get bidirectional asociations and everything working, and you also do have a state per route. I think that’s also an awesome approach, but I checked it out and I decided for this Angular’s scope inheritance approach as it’s cleaner in my opinion. And you always have some coupling. Your directives will depend on the route state, which is an imlpicit dependency. Also, having a directive which has an isolated scope SHOULDN’T deppend on a state from the route either, otherwise, you’re adding some coupling and that directive will cease to be a reusable component. If you want it to be a reusable component, you need isolated scope, isolated route dependency as well in my opinion.
      And if you’re making a directive which is not isolated, then why not go with scope inheritance which is simpler thatn AngularUI’s routing?

      That’s my opinion :) (Long one :P).

      Thanks :) Glad u liked Restangular.

      • Kevin

        Thanks for the great answer, that made everything really clear. Say I wanted to use the isolated directives method with a parent controller, then add socket io and an event emitter for a chat feature. Would there be any problem in using both methods? You mentioned sending a notification and assigning it on a local scope. So based on that I would imagine setting up a broadcast situation to rootscope and adding a $watch onto the top-level controller’s scope like you had suggested. Do you see any problems with this approach using socket io?

        • mgonto

          No, no problems. You just need to use $emit, $broadcast and $on for event sending and $watch for changes notification.

          • Kevin

            I recently went back to rewrite something with this approach and, just to be clear, you’re utilizing a parent controller (say right after you define the app), then you’re using routing to define child controllers, and once you have something you need to make ‘global’ you just do $scope.$parent.var = $scope.var in the child controller?

          • Martin Gontovnikas

            no, I never call $parent.

            I do have a “global” view controller.

            Then I have a controller defined with router.

            If I need something shared among all, I resolve this in the Global controller as soon as I can. If I need a “trigger” I put a method in global controller that will be called from child controlelr which will set the variable in the global controll’ers scope, and therefore to the children as well.

  • http://adaapasaja.com/ Ada apa saja

    thanks for the info and suggestions hopefully I can apply

  • finestglasses j.

    In the internet vendors severe prescription cups, you’ll receive most of these Designer Glasses on method affordable prices compared to those at the bodily retailers.