2

I have slightly modified the example from the following URL (http://docs.angularjs.org/cookbook/helloworld) as follows, placing the name value within an attrs object property:

<!doctype html>
<html ng-app>
  <head>
    <script src="http://code.angularjs.org/1.2.9/angular.min.js"></script>
    <script>
      function HelloCntl($scope) {
        $scope.attrs = {
            name : 'World'
        }
      }
    </script>
  </head>
  <body>
    <div ng-controller="HelloCntl">
      Your name: <input type="text" ng-model="attrs.name"/>
      <hr/>
      Hello {{attrs.name || "World"}}!
    </div>
  </body>
</html>

One benefit I can see is that the HTML source code can be searched for /attrs\.\w+/ (e.g.) if there is ever a need to easily find all such attributes within the view rather than the controller (e.g. a search for name could collide with form element names). Also within the controller I can only imagine that partitioning attributes necessary for the front end might lend itself to better organization.

Is anybody else using such a level of abstraction. Are there any possible specific further benefits to it's usage? And most importantly, might there be any specific drawbacks to it.

Dexygen
  • 12,287
  • 13
  • 80
  • 147
  • @Patrick Evans Consider converting your comment to an answer and I can upvote it, since it shows that an advantage of the approach I'm asking about, is that similar namespace/partitioning can be likewise applied to other subsystems within an overall Angular app – Dexygen Jan 22 '14 at 14:46
  • Done, also included sample code and example of how i also do this for services. – Patrick Evans Jan 22 '14 at 15:01

2 Answers2

2

It's recommended that you always use a dot in your ngModels in order to avoid potential issues with prototypal inheritance that are discussed in Angular's Guide to Understanding Scopes:

This issue with primitives can be easily avoided by following the "best practice" of always have a '.' in your ng-models – watch 3 minutes worth. Misko demonstrates the primitive binding issue with ng-switch.

Prototypal inheritance and primitives

In javascripts' approach to inheritance reading and writing to a primitive act differently. When reading, if the primitive doesn't exist on the current scope it tries to find it on any parent scope. However, if you write to a primitive that doesn't exist on the current scope it immediately creates one on that scope.

You can see the problem this can cause in this fiddle that has 3 scopes- one parent and two children that are siblings. First type something in the "parent" and you'll see that both children are updated. Then type something different in one of the children. Now. only that child is updated, because the write caused the child to creates it's own copy of the variable. If you now update the parent again, only the other child will track it. And if you type something into the sibling child all three scopes will now have their own copies.

This can obviously cause lots of issues.

Prototypal inheritance and objects

Try the same experiment with this fiddle in which each ngModel uses a property of an object instead of a primitive. Now both reading and writing act consistently.

When you write to a property of an object it acts just like reading does (and the opposite of how writing to a primitive does). If the object you're writing to does not exist on the current scope it looks up it's parent chain trying to find that object. If it finds one with that name then it writes to the property on that found object.

So, while in the primitive example we started with 1 variable and then after writing to the children ended up with 3 copies of the variable- when we use an object we only ever have the one property on the one object.

Since we almost always, perhaps just always, want this consistent behavior the recommendation is to only use objects properties, not primitives in an ngModel or, said more commonly, "always use a dot in your ngModel"

KayakDave
  • 24,636
  • 3
  • 65
  • 68
  • To extend further Kayak's explanation, check out the following link:http://stackoverflow.com/questions/14049480/what-are-the-nuances-of-scope-prototypal-prototypical-inheritance-in-angularjs/14049482#14049482 You'll have a background on why and how using a "dot" in your ngModel is a safe bet. – roland Jan 22 '14 at 15:02
  • My initial impression is that this is an outstanding answer, however it's largely over my head since I am such an Angular newbie that I am still referring to the Hello World example :( – Dexygen Jan 22 '14 at 15:33
  • @GeorgeJempty, Javascript's prototypal inheritance is pretty confusing. See if my updated answer is clearer. I recommend playing with the two fiddles I included. It's often a shock when people first see the way in which writing to a primitive changes the behavior. But as you'll see you can avoid lots of problems when it finally makes sense. – KayakDave Jan 22 '14 at 16:52
  • Great explanation, too bad it's not enforced by the framework and too bad the examples demonstrate bad practice – Dexygen Jan 23 '14 at 15:07
  • See #1 here: http://nathanleclaire.com/blog/2014/04/19/5-angularjs-antipatterns-and-pitfalls/ – Dexygen Jan 11 '16 at 16:56
2

I do this as well. I also put all action functions (button clicks etc) into a $scope.actions object. And since i use socket.io i put those callbacks into a $scope.events object it usually keeps my controllers nice and organized and easily able to find the function i need to if i need to do any editing.

app.controller('Ctrl',['$scope', function ($scope) {
    $scope.data = {
        //contains data like arrays,strings,numbers etc
    };

    $scope.actions = {
        //contains callback functions for actions like button clicks, select boxes changed etc
    };

    $scope.events = {
        //contains callback functions for socket.io events
    }
]);

Then in like my templates I can do like

<input ng-click="actions.doSomething()">

I also do a partial of this for services. I use a private and public data object

app.factory('$sysMsgService',['$rootScope',function($rootScope){
    //data that the outside scope does not need to see.
    var privateData = {};
    var service = {
        data:{
            //contains the public data the service needs to keep track of
        },
        //service functions defined after this
    };
    return service;
}]);
Patrick Evans
  • 41,991
  • 6
  • 74
  • 87