Why does a function behave like a computed?
Asked Answered
K

2

6

Given the following HTML/JS (http://jsfiddle.net/mgs_jsfiddle/gUhm2/)

<div data-bind="foreach: users">
    <p data-bind="click: $root.onClick">
        <span data-bind="text: id"></span>
        <span data-bind="text: firstName"></span>
        <span data-bind="text: $root.isSelected($data)"></span>
    </p>
</div>

$(function() {
    function ViewModel() {
        var self = this;
        self.users = [
            { id: 1, firstName: "Bob" },
            { id: 2, firstName: "David" },
            { id: 3, firstName: "Walter" }
            ];
        self.selectedId = ko.observable(1);
        self.isSelected = function(user) {
            return user.id === self.selectedId() ? "YES" : "NO";
        };
        self.onClick = function(user) {
            self.selectedId(user.id);
        }
    };
    ko.applyBindings(new ViewModel());
});

A list is shown. By clicking a row, the row's id is stored into selectedId.

I do not understand, why the function isSelected is re-evaluated, when the selectedId is changed. After all, this is no computed. Why is it re-evaluated anyway?

Kopans answered 13/9, 2013 at 16:23 Comment(0)
T
6

That happens because isSelected() method accesses selectedId property (which is observable). Consider this:

HTML

<!-- adding into the foreach: block  -->
<span data-bind="text: $root.someFunc()"></span>
<span data-bind="text: $root.someOtherFunc()"></span>

JS

// adding to a model definition
self.someFunc = function() {
    self.selectedId();
    console.log('I AM AN OBSERVER');
    return 'oi: ' + Math.random();
};
self.someOtherFunc = function() {
    // self.selectedId();
    console.log('AND I AM NOT');
    return 'no: ' + Math.random();
}

Fiddle.

As you see, the only difference between those functions is that the first one does check the value of a model property defined as ko.observable. So each time self.selectedId is changed, this function is notified about it (which effectively means its rerun).

Note that if you drop the corresponding data-bind part, this method won't be run in the view initialization phase, thus failing to register as a proper observer.

The second method, though invoked in the initialization phase too, doesn't attempt to check selectedId value - so it's not registered as this value's observer, and it's not invoked subsequently.

Twophase answered 13/9, 2013 at 16:48 Comment(3)
I do understand. However, I would not have thought so after reading the documentation. So what is the reason for having computed observables at all, if a normal function that accesses the base observables does the same thing? Looking at knockoutjs.com/documentation/computedObservables.html, fullName is coded as a computed. Wouldn't the result be the same, if it had been coded as a normal function?Kopans
Now that question is already answered. )Twophase
It is good that Knockout behaves that way. So it is possible to write a global function that operates on data from the current row. Could the function also be written as a computed? I do not know how $data might be handed over to the computed.Kopans
H
1

Knockout uses computed observables to update the bindings. It's something like this (modified from bindingAttributeSyntax.js):

ko.computed({
    read: function() {
        bindingHandler.update(node, getValueAccessor(bindingKey), allBindings,
            bindingContext.$data, bindingContext);
    },
    disposeWhenNodeIsRemoved: node
});

So any observables that you access to get the value of the binding will become dependencies of this computed observable and cause the binding to update.

Hellion answered 13/9, 2013 at 20:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.