24

According to https://github.com/angular/angular.js/wiki/Understanding-Scopes, it's a problem to try to data-bind to primitives attached to your $scope:

Scope inheritance is normally straightforward, and you often don't even need to know it is happening... until you try 2-way data binding (i.e., form elements, ng-model) to a primitive (e.g., number, string, boolean) defined on the parent scope from inside the child scope. It doesn't work the way most people expect it should work.

The recommendation is

This issue with primitives can be easily avoided by following the "best practice" of always have a '.' in your ng-models


Now, I have this very simple setup which violates these rules:

HTML:

<input type="text" ng-model="theText" />
<button ng-disabled="shouldDisable()">Button</button>

JS:

function MyController($scope) {
    $scope.theText = "";
    $scope.shouldDisable = function () {
         return $scope.theText.length >= 2;
    };
}

Is this really bad? Is this going to screw me over in some horrible way when I start trying to use child scopes, somehow?


Do I need to change it to something like

function MyController($scope) {
    $scope.theText = { value: "" };
    $scope.shouldDisable = function () {
         return $scope.theText.value.length >= 2;
    };
}

and

<input type="text" ng-model="theText.value" />
<button ng-disabled="shouldDisable()">Button</button>

so that I follow the best practice? What concrete example can you give me where the latter will save me from some horrible consequence that would be present in the former?

Domenic
  • 110,262
  • 41
  • 219
  • 271

2 Answers2

19

A lot of things introduce new scopes. Let's say that in your controllers, you actually want to add tabs : first tab is actual rendering, second tab is the form (so that you have a real-time preview).

You decide to use a directive for that :

<tabs>
  <tab name="view">
    <pre>{{theText|formatInSomeWay}}</pre>
  </tab>
  <tab name="edit" focus="true">
    <input type="text" ng-model="theText" />
  </tab>
</tabs>

Well, know what ? <tabs> has its own scope, and broke your controller one ! So when you edit, angular will do something like this in js :

$scope.theText = element.val();

which will not traverse the prototype chain to try and set theText on parents.

EDIT : just to be clear, I'm only using "tabs" as an example. When I say "A lot of things introduce a new scope", I mean it : ng-include, ng-view, ng-switch, ng-controller (of course), etc.

So : this might not be needed as of right now, because you don't yet have child scopes in that view, but you don't know whether you're gonna add child templates or not, which might eventually modify theText themselves, causing the problem. To future proof your design, always follow the rule, and you'll have no surprise then ;).

Ven
  • 19,015
  • 2
  • 41
  • 61
  • OK, so the preconditions for getting screwed seem to be that there's a child scope which also references `theText`. Is that correct? – Domenic Jun 19 '13 at 16:23
  • That modifies it*. Simply referencing it is okay. – Ven Jun 19 '13 at 16:41
  • So in my example from the original post, I should be OK, and will not get screwed, since my template does not contain any child directives that reference `theText`? – Domenic Jun 19 '13 at 17:13
  • 2
    Exactly. The dot rule (imho) still applies because you don't know whether you're gonna add child templates in the future. – Ven Jun 19 '13 at 18:26
  • I was going to add an answer to this question, but that last comment covers what I wanted to add -- using the dot rule future proofs your design. – Mark Rajcok Jun 19 '13 at 19:09
  • OK, that makes some sense. If you want to update the answer to talk about future-proofing and how it all hinges on whether you're adding child templates later, that would help it come back to answering the original question, which was intended to be "I'm doing something super-simple, does the dot rule still apply?" – Domenic Jun 19 '13 at 19:18
  • This egghead video has a fantastic example of this issue in action: https://egghead.io/lessons/angularjs-the-dot – Ben Barreth Oct 29 '14 at 14:27
6

Let's say you have scopes M, A, and B, where M is the parent of both A and B.

If one of (A,B) tries to write to M's scope, it will work only on non-primitive types. The reason for this is that non-primitive types are passed by reference.

Primitive types on the other hand, are not, hence attempting to write to theText on M's scope will create a new property of the same name on A or B's scope, respectively, instead of writing to M. If both A and B depend on this property, errors will happen, because neither one of them would be aware of what the other one is doing.

holographic-principle
  • 19,688
  • 10
  • 46
  • 62
  • Yeah, I kind of understand the problem theoretically, but I don't see how it affects this simple example in practice. E.g. "If both A and B depend on this property"---when would that be true? An example building upon mine would be excellent. – Domenic Jun 18 '13 at 21:20
  • Let's say you had an app-level controller, and you had ng-view inside it, where you loaded separate controllers depending on routes. Then you might want some state preserved on the main controller after switching routes. That's one example. – holographic-principle Jun 18 '13 at 21:25
  • Some directives like ng-include create their own scope. Another place where things could go wrong. – holographic-principle Jun 18 '13 at 21:28
  • So in that example, I would have to have two separate routes both of which want to modify `theText` for this problem to occur? – Domenic Jun 18 '13 at 21:30
  • It's enough if one has to modify it, if both need to read it. – holographic-principle Jun 18 '13 at 21:31
  • It sounds like this is not really applicable to my case of an `` and a ` – Domenic Jun 18 '13 at 21:33
  • If you do not use child scopes at all, then it does not touch you at all. But in real-world situations you will use them :). – holographic-principle Jun 18 '13 at 21:34
  • ng-repeat was also known to fail if working with arrays of primitives. I don't know if that was mended in some way, but it's relevant to know that the problem arises from the same place. (ng-repeat creates a separate scope) – holographic-principle Jun 18 '13 at 21:39
  • @Domenic i've been bitten by this. In my example "foo" scope, inside a controller that keeps a list of things rendered with ng-repeat. Then also trying to have a edit view that is a child scope of the "foo" that wants to edit a particular element. Binding show up the information because of inheritance, but then when i tried to "edit" things it just created a local version of the properties and not actually changing the list in "foo" – Chris Matheson Jun 19 '13 at 08:15
  • I finally solved applying some of the concepts here - I had trouble accessing the datevalue from a calendar and declaring the model as $parent.datetimeValue solved all my trouble to access the variable, and accessing them as $scope.$parent.datetimeValue was fine from all child scopes. – PayToPwn May 06 '16 at 09:28