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?
In 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.org/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 comparing an old value to a new value. KnockoutJS instead is just tracking value changes so no comparison is required in any way.
here's an example fiddle https://jsfiddle.net/gatnfbao/1/
var ViewModel = function(first, last) {
this.firstName = first;
this.lastName = ko.observable(last);
this.fullName = ko.computed(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 computed field is using this.firstName and this.lastName(), it will tell knockout that the computed field is basing it self on lastName. What happens is that when knockoutjs boots, it will try to call every ko.computed variables to add them to the dependency tree. You can see that in our viewModel, the field fullname is correctly computed: Planet Earth!. Yet if you modify the field in the input text, it will not change anything in the ko.computed. 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 computed 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.net/gatnfbao/2/
var ViewModel = function(first, last) {
this.firstName = first;
this.lastName = last;
this.fullName = ko.computed(function() {
return this.firstName + " " + this.lastName;
}, this);
};
ko.applyBindings(new ViewModel("Planet", "Earth")); // This makes
Notice that now the computed field is using this.lastName not this.lastName(). Now knockout doesn't have any idea to what this computed value is depending on.
The ko.computed will be called once with the values Planet and Earth, but as it isn't using an observable, ko.computed 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 computed with the initial value but it wasn't containing any observable.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With