I am trying to understand what is the difference between keeping the observable a function or setting it as an object
My observable item:
self.SelectedItem = ko.observable();
My view:
<div class="collapse" data-bind="with: SelectedItem">
@Html.Action("Profile", "Team")
</div>
<div class="collapse" data-bind="with: ItemForEditing">
@Html.Action("EditProfile", "Team")
</div>
selecting the item from grid :
ko.utils.extend(TeamManager.prototype, {
selectItem: function (item) {
if (typeof item == "undefined") return;
this.SelectedItem(item);
//this.SelectedItem = item;
ko.mapping.fromJS(ko.mapping.toJS(item), this.ItemForEditing);
},
acceptItem: function (itemData) {
ko.mapping.fromJS(itemData, {}, this.SelectedItem);
},
Why is my binding on the view only working if observable SelectedItem
is called as a function, eg this.SelectedItem(item);
, and not as an assignment, eg this.SelectedItem = item;
. The latter causes the partial view to not render.
If I use it like this this.SelectedItem(item);
it's all ok - why do I need to use it this way?
I am trying to understand what is the difference between keeping the observable a function or setting it as an object
My observable item:
self.SelectedItem = ko.observable();
My view:
<div class="collapse" data-bind="with: SelectedItem">
@Html.Action("Profile", "Team")
</div>
<div class="collapse" data-bind="with: ItemForEditing">
@Html.Action("EditProfile", "Team")
</div>
selecting the item from grid :
ko.utils.extend(TeamManager.prototype, {
selectItem: function (item) {
if (typeof item == "undefined") return;
this.SelectedItem(item);
//this.SelectedItem = item;
ko.mapping.fromJS(ko.mapping.toJS(item), this.ItemForEditing);
},
acceptItem: function (itemData) {
ko.mapping.fromJS(itemData, {}, this.SelectedItem);
},
Why is my binding on the view only working if observable SelectedItem
is called as a function, eg this.SelectedItem(item);
, and not as an assignment, eg this.SelectedItem = item;
. The latter causes the partial view to not render.
If I use it like this this.SelectedItem(item);
it's all ok - why do I need to use it this way?
-
And could you tell us what is stored in
item
? – Loïc Faure-Lacroix Commented Jan 29, 2016 at 10:12 -
item
it is been taken from griddata-bind="click: $selectItem"
it's an object with all properties observable self.TeamsModel($.map(data, function (item) { return ko.mapping.fromJS(item); })); – Mursa Catalin Commented Jan 29, 2016 at 10:17 -
All I can say is that it's hard to know why it's not rendering. Usually when something is wrong with knockout, you should see an error in the console. Anything? if you do
console.log(item)
what does it show?. May be the format of the object isn't correct. Most likely that somewhere you're usingthis.SelectedItem
as an observable. If you set an object, somewhere, it will try to callSelectedItem
and crash because objects aren't callable. – Loïc Faure-Lacroix Commented Jan 29, 2016 at 10:32
2 Answers
Reset to default 4In knockout.js, all observables are formed as a function containing a data. As there was no really easy wait to track changes on object with =
, the dev of knockoutjs decided that instead of using getter/setter methods on objects, the use of function would be the easiest way to achieve tracking changes.
Getter/Setter on objects are new features that aren't available on older browsers, but calling a JavaScript function is available and you have access to the method that called this method aka arguments.callee
.
https://developer.mozilla/fr/docs/Web/JavaScript/Reference/Fonctions/arguments/callee
In order to be able to achieve tracking, whenever you call the observable method, it will build a dependency tree using the callee and this way, KnockoutJS is able to know what to update whenever en observable is being called.
Let say you have this object:
t.fun = ko.Observable(10)
Whenever, you use the method. t.fun()
you're actually calling the method but knockout would register this place as a place that requires to be called letter when the value of fun
will change.
When you call t.fun(value)
it will trigger a change and will call again those places that were already called.
This lead to why on knockout, every observable have to be called at least once, to catch any change. If you don't call in any way, t.fun()
anywhere, knockoutjs doesn't have any idea that this part of your code depends on t.fun
.
Also, whenever you affect t.fun
to anything else..
t.foo = t.fun
.
t.foo
should work exactly like t.fun
.
For that reason, if you set a value in your object. It's always a value an unlike some other languages, t.fun = 1
isn't updating t.fun
but replacing the value of t.fun
by 1
. It's possible that JavaScript could do update on basic types, but in the case of Objects, you're affecting a plain new value and there is nothing JavaScript can do to know if the value changed.
AngularJS on the other hand, is checking for a value change by paring an old value to a new value. KnockoutJS instead is just tracking value changes so no parison is required in any way.
here's an example fiddle https://jsfiddle/gatnfbao/1/
var ViewModel = function(first, last) {
this.firstName = first;
this.lastName = ko.observable(last);
this.fullName = ko.puted(function() {
// Knockout tracks dependencies automatically.
// It knows that fullName depends on firstName and lastName,
// because these get called when evaluating fullName.
return this.firstName + " " + this.lastName();
}, this);
};
ko.applyBindings(new ViewModel("Planet", "Earth")); // This makes
You can see that the puted field is using this.firstName
and this.lastName()
, it will tell knockout that the puted
field is basing it self on lastName
. What happens is that when knockoutjs boots, it will try to call every ko.puted
variables to add them to the dependency tree. You can see that in our viewModel, the field fullname is correctly puted: Planet Earth!
. Yet if you modify the field in the input text, it will not change anything in the ko.puted
. But if you change the "Last Name" field, it will update the fullName
taking into account of both fields because they are both present. And if you modify firstName
you'll see the result only and only when lastName
is changed.
But now, we'll change a bit the code to make you understand that when you modify lastName
you see that the puted value use the correct value of firstName
as a side effect of lastName
being an observable but firstName
wasn't... Lets remove all observables! https://jsfiddle/gatnfbao/2/
var ViewModel = function(first, last) {
this.firstName = first;
this.lastName = last;
this.fullName = ko.puted(function() {
return this.firstName + " " + this.lastName;
}, this);
};
ko.applyBindings(new ViewModel("Planet", "Earth")); // This makes
Notice that now the puted field is using this.lastName
not this.lastName()
. Now knockout doesn't have any idea to what this puted
value is depending on.
The ko.puted
will be called once with the values Planet
and Earth
, but as it isn't using an observable, ko.puted
has no idea what's going on in there. For that reason, if you modify the field firstName
or lastName
, then fullName
is never updated. It was puted with the initial value but it wasn't containing any observable.
That's just how observables work. A knockout observable turns your property/variable into a function. To get the value from it, you have to call the function with no parameters. To set the value, you call it with the new value. The act of calling the function with the new value allows knockout to update any subscriptions, including your bindings.
When you do:
this.SelectedItem = item;
SelectedItem
is no longer an observable
, it's been updated to point directly at the item
, so no bindings will update.
Having said that, if you're working in a modern environment, one of the main authors of knockout has made a plugin that allows more usual property access, where an assignment would work.