3

I have seen other threads on this but I'm still confused and I think I am presenting a different case here.

I'm using the revealing pattern to return a view model object to my HTML document. Thus, I have a view model object that looks something like this:

var vm = function() {
   var customProperty = ko.numbericObservable(0);

   return {
      customProperty: customProperty
   };
} ();

From this, you can see that customProperty is being assigned to a Knockout numeric observable with an initial value of 0.

In the HTML document that includes the JavaScript above, I have a SPAN element with a data-bind attribute that subscribes to the customProperty observable, like this:

<span data-bind="text: customProperty" 
  id="customProperty" style="font-weight:bold"></span>

So far, so good. The above works just fine, meaning that whenever I change the value of customProperty in script, the text in the SPAN updates right away. For example, I can successfully and easily use this expression to change the value of the customProperty observable from 0 to 10:

vm.customProperty(10);

My questions:

  1. Notice that I did not use parentheses when referring to the customProperty value in the data-bind attribute. Why are parentheses not required?

  2. I discovered that using parentheses also works:

I understand why using parentheses works (because I am reading the value of a Knockout observable). But why are the parentheses not NEEDED? In other words, why would the data-bind expression in point 1 work at all?

  1. Finally, what actually is going on in this assignment?

    var customProperty = ko.numericObservable(0);

Does customProperty end up holding a pointer to a function called customProperty()?

JotaBe
  • 34,736
  • 7
  • 85
  • 109
Jazimov
  • 10,807
  • 8
  • 45
  • 50

1 Answers1

6
  1. When ko parses the bindings, it checks if the expression is an observable, which, as you know is a function. If the expression is an observable, ko automatically unwraps the value to show it, but it also allows subscriptions and notifications.

  2. In this case, when ko parses the expression, it finds a value, not an observable, so, it also works correctly to show the value (value changes > view updates). However, you'd lose the binding from the view to the value (input value changes > observable is not updated), because it's not an observable. For more details, see explanation and snippet below.

  3. customProperty is a function, in particular a ko.observable, which means that supports subscriptions and notifications, apart form reading or setting the value using the () or (newValue) syntax

NOTE: the difference between using and not using parentheses is huge. If you do this:

<input type="text" data-bind="value: customProperty" ...

as I explain in 1, ko finds that customProperty is an observable, so when the user changes the value in the input, the new value is written back to the observable. If you do this:

<input type="text" data-bind="value: customProperty()" ...

as I explain in 2, ko finds a value, not an observable. So, if the user changes the value of the input by typing on it, the new value is not fed back to the observable, because ko doesn't know it's an observable. (But if the observable value is updated, the view changes, because the dependency is discovered and subscribed during the expression evaluation).

var vm = {
 customProperty: ko.observable(10)
};

ko.applyBindings(vm);
body {
  font-family: Segoe, Arial
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

data-bind="value: customProperty()"<br/>
<input type="text" data-bind="value: customProperty(), valueUpdate: 'keyup'"/><br/>
If you change the input text, customProperty is not updated
<br/><br/>
data-bind="value: customProperty"<br/>
<input type="text" data-bind="value: customProperty, valueUpdate: 'keyup'"/><br/>
If you change the input text, customProperty changes
<br/><br/>
customProperty value: <span data-bind="text: customProperty"/>

In other frameworks, like Angular, instead of using functions, it uses properties with JavaScript setters and getters so that the syntax never needs parentheses. Properties with settes and getters are read and written as any other property, but behind the scenes the code in the setter or getter runs, allowing subscriptions and notifications to happen.

NOTE 2 (because of question in the comment). You can think of it like this: when ko parses a binding expression, it evaluates the whole expression at once and checks if the result is an observable. So, when you have an expression like this: customProperty == 10, when ko evaluates it, it finds it's not an observable (but a boolean) and takes no extra steps to get the value. The result will always be false, because customProperty is a function, and thus is '!= 10'. If you change the expression to this one: customProperty() == 10 the custom property value will be unwrapped by the (), and the comparison will work as expected. By the way, try not to include code in the binding expressions: it's much better to use computed observables (better pure computeds, if possible) in your model.

Console experiment for note 2

Type: var vm = {customProperty: ko.observable(10)} to create a view model.

Type: vm.customProperty(), and you'll see 10 as a result.

Type: vm.customProperty, and you'll see function ... as a result.

Type: vm.customProperty() == 10, and you'll see true (no wonder, 10 == 10)

Type: vm.customProperty == 10, and you'll get false (because function != 10)

Furthermore, type ko.isObservable(vm.customProperty) and you'll see true. That's what ko does. So ko knows it must unwrap the value. Type ko.unwrap(vm.customProperty) and you'll see 10

Finally, type ko.isObservable(vm.customProperty == 10) or ko.isObservable(vm.customProperty() == 10). In both cases you'll get false, becase the expression is a bool in both cases, and not an observable function. Ko doesn't decompse the expression and check it piece by piece. That would be the only way to discover in the first expression that customProperty is an observable and should be unwrapped. But ko does not do it in that way.

NOTE 3: expressions, as computed observables, are re-evaluated when an observable property was used in the original evaluation and it changes its value. Be warned that if in the first evaluation you only access an observable property, even if the code contains references to other observables, it will only be re-evaluated when the accessed observable changes its value. Changes on the other observables won't be "observed". The typical case is an if that depends on different observables depending on the executed branch

JotaBe
  • 34,736
  • 7
  • 85
  • 109
  • Very clear. When trying to read the value of customProperty in the page's JS, it seems that it would be okay to use expressions like "if (customProperty == 10)..." but I routinely see only examples of "if (customProperty() == 10)..." Shouldn't both work, given the explanation above? It seems that there would be no risk to wiring up a binding in "if (customProperty) == 10)..." because 10 is a hardcoded value. Would "if (customProperty) == specialValue)..." set up a binding between customProperty and specialValue so that future changes to specialValue get pushed into customProperty? – Jazimov Dec 16 '15 at 13:20
  • In Note 2: Above I got the impression that you explained how customProperty and customProperty() both could evaluate to a value (say, 10); in this note, however, you say that customProperty cannot be evaluated because you stated the expression "if (customProperty==10)" would always evaluate to false. This is confusing. To clarify, I am not trying to use this if statement in a binding expression but do intend to use it within JS code on the HTML page. It seems that binding expressions let you eval customProperty but JS requires that you use customProperty()... – Jazimov Dec 17 '15 at 13:40
  • I'll try to explain note in other words. ko takes the whole binding expression, like `customProperty == 10` and evaluates it. If you do this in your browser js console: `var a = ko.observable(10);` and then type `a`, you'll see `function observable...`. So, `10` and `function` are not equal. If you type `a()` the console will show `10`. That's why `a()==10` is `true`. The point is that ko evals the whole expression at once, so, 'a==10' will yield false. If ko evaluated it step by step, it could discover that `a` is an observable, unwrap it, and compare it. But ko does it in a single step... – JotaBe Dec 17 '15 at 16:25
  • I've included something for you to help you undertsand note 2. Hope this time you get it. – JotaBe Dec 17 '15 at 16:43
  • > However, you'd lose the binding from the view to the value, because it's not an observable I believe this is incorrect, at least the "lose the binding" part. You make it sound like `data-bind="foo: myProperty()"` won't update when `myProperty` changes (since `myProperty()` isn't an observable)... but it will. – Retsam Dec 17 '15 at 16:58
  • @Retsam, I've added a code snippet so that you can see what I explain in my answer. I say "from the view to the value" not "from the value to the view" – JotaBe Dec 17 '15 at 23:48
  • It took all the added detail for me to really understand this topic. Key for me was understanding the ko.isObservable(...) function and what KO does to unwrap values. I hope many others will benefit from your many excellent examples here. – Jazimov Dec 18 '15 at 00:56