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:
Notice that I did not use parentheses when referring to the customProperty
value in the data-bind attribute. Why are parentheses not required?
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?
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()
?
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.
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.
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