26

I remember seeing this famous quote from a video on AngularJS saying that should be always using a . (dot) in your models.

Well I am trying to follow this say I have

   var item = {}
   item.title = "Easy Access to support";
   item.available = true;
   item.price = 31.67;

So this works great in my view i do

  {{ item.title }}
  {{ item.available }}

I am using a dot so I think this is good.

But I have some properties that I don't consider part of the model but maybe I am wrong. For example I have a property I use to enable or disable a button using the ng-disable, I have entered this using dot format. Its basically entered like so

 $scope.disableButton = true;

and I use it like

 ng-disable="disableButton"......

Should I make this part of the model "item" ? or create another js object just so i can hold this property using a dot ?

Anybody know if this acceptable or should I be doing everything (even these simple properties) with a .dot ??

Thanks

isherwood
  • 58,414
  • 16
  • 114
  • 157
Martin
  • 23,844
  • 55
  • 201
  • 327
  • 3
    Here's the exact place in the video where Miško mentions you need a "dot" with ng-model: http://www.youtube.com/watch?v=ZhfUv0spHCY&feature=youtu.be&t=32m51s – Mark Rajcok Aug 08 '13 at 15:50
  • One place where the "use a dot" falls apart is if your directive takes a ngModel but wraps other directives and passes ngModel through to the other directives. The dot notation gets lost as soon as the first pass happens, because the variable gets renamed to "ngModel" (or whatever alias you set) as soon as its passed to the first directive. From there on, its known as "ngModel" which is absent of dot notation and is treated as a primitive, which affects its ability to be passed further down the nested directive chain – Erin Drummond Apr 09 '14 at 23:15

2 Answers2

48

The "there should always be a dot in your model" refers to ngModel. This directive does two-way binding. If you two-way bind to a primitive (such as a Boolean in your case), the setter will set it on the current scope rather than the scope on which it is defined, which can cause a headache when you have a large user-interface with a lot of child scopes. It does not refer to other directives such as ngDisable. See this explanation for more details on this specific issue.

Sample scenario: a parent scope with $scope.foo = "bar", and a child scope with a <input type="text" data-ng-model="foo">. It will display bar initially, but once the user changes the value, a foo will be created on the child scope and the binding will read and write that value. The parent's foo will remain bar. Hope that summarises it well.

So for ngModel purposes, you might have to create an object to work around such binding issues, but for any other directive you should have the regular, logical grouping.

Steve Klösters
  • 9,427
  • 2
  • 42
  • 53
  • 3
    'controllerAs' syntax works great for this purpose (and in general to avoid scope confusion). – GBa Dec 14 '14 at 20:27
  • 1
    Clearest, most succinct explanation I've seen for this. – SWalters Jan 28 '16 at 17:21
  • 1
    Great Explanation! Thanks. – mattfred Feb 16 '16 at 01:19
  • But why? Why does `foo` get created on the child scope when `something.foo` doesn't? – HughHughTeotl Apr 10 '16 at 22:55
  • 9
    This is due to how object properties are accessed in JavaScript. Simply put, in JavaScript, `$scope.foo = "bar"` means "set property foo of object $scope to value "bar". `$scope.foo.bar = "baz"`, however, means "set property bar of object foo of object $scope to "baz". The important difference here is in the first example, `foo` is a property to set and in the second example `foo` is an object to get. Setting never goes up the prototypal chain, getting can go up the prototypal chain if it's not found. The same thing applies in `ngModel` with the value `foo` vs. `foo.bar`. Hope this helps. – Steve Klösters Apr 21 '16 at 08:55
0

Here's a situation where a dot is needed.

When you have a $scope value that you want to use as a ngModel value (let's call it selectedItem), you might be tempted to just create $scope.selectedItem and pass that to the template. However, this is dangerous if the template creates child scopes.

There are certain AngularJS directives that create child scopes:

  • ngRepeat
  • ngIf
  • ngController
  • ...and others (their doc page will say "This directive creates new scope" under the Usage heading).

Child scopes are dangerous because of how scope inheritance works. The relationship is this:

-- $parent scope
   ├ selectedItem
   └─ $child scope

As a child scope, the $child object prototypically inherits from $parent. That's a javascript term that basically means you can get any $parent property by getting $child.<property>. But you cannot set values, which is the problem. This is just how javascript works.

So a template can access $parent.selectedItem by reading $child.selectedItem. But if the template sets $child.selectedItem, it sets it on $child not $parent, so now you have two versions of selectedItem:

-- $parent scope
   ├ selectedItem
   └─ $child scope
      └ selectedItem

And ngModel directives both get and set the scope value. The getting works, but the setting breaks things (ss others have explained).

Why using a dot solves the problem

When you store the selectedItem value with a dot on the parent scope (e.g. $scope.vm.selectedItem, then the child scope template only ever gets the vm object.

Using vm, the relationship looks like this:

-- $parent scope
   ├ selectedItem
   │ └─ vm
   │     └─ selectedItem
   └─ $child scope

The $child scope only ever reads the vm object; it never writes a new vm object (in JS, objects are references not values). And prototypical inheritance is only involved in accessing vm. After a scope gets the vm object, it can directly use its values without any prototypical inheritance.

Think of it as passing around an object between scopes.

Matthias
  • 13,607
  • 9
  • 44
  • 60