ko.computed with passed argument shows function, but not a value

Kamilius picture Kamilius · Apr 26, 2013 · Viewed 8.7k times · Source

My task is to dynamicly form "href's" for links, each time an attached observable is changed. Here is the link for an example: JS Fiddle example link

I've met two problems when achieving this:

  • when i try to pass some string + computed observable, i get computed function listing, instead of value of it.

    <a data-bind="attr: {href : '#someHash/' + getHref(10)}">Link</a>
    

    Link looks like:

    http://fiddle.jshell.net/3DAfQ/1/show/#someHash/function h(){if(0<arguments.length)return"function"===typeof v?v.apply(d,arguments):j(Error("Cannot write a value to a ko.computed unless you specify a 'write' option. If you wish to read the current value, don't pass any parameters.")),this;n||g();b.r.Wa(h);return l}
    

    Which i find not even close to appropriate.

  • second, when i try to change an observable, computed depends on, link doesn't change.

    <a href="#" data-bind="click: changeStoreHref(20)">change Link</a>
    
    self.changeStoreHref = function(num)
    {
        self.storeHref(num);
    };
    

Here is HTML code:

<a data-bind="attr: {href : '#someHash/' + getHref(10)}">Link</a>
<a href="#" data-bind="click: changeStoreHref(20)">change Link</a>

And knockoutjs:

function viewModel()
{
    var self = this;
    self.storeHref = ko.observable('ten');

    self.getHref = function(id)
    {
        return ko.computed({
            read: function()
            {
                self.storeHref(id);
                return self.storeHref();
            }
        });
    };

    self.changeStoreHref = function(num)
    {
        self.storeHref(num);
    };
}

ko.applyBindings(new viewModel());

I remind, that you can check this example on following link: JS Fiddle example link Thanks.

Answer

delixfe picture delixfe · Apr 26, 2013

A working version might look like:

HTML:

<a data-bind="attr: {href: link}">Link</a>
<a href="#" data-bind="click: changeStoreHref">change Link</a>

JavaScript:

function viewModel()
{
    var self = this;
    self.storeHref = ko.observable(1);

    self.link = ko.computed(function() {
        return '#someHash/' + self.storeHref();
    });

    self.changeStoreHref = function() {
        self.storeHref(self.storeHref() + 1);
    };
}

ko.applyBindings(new viewModel());

Fiddle: http://jsfiddle.net/3DAfQ/6/

The reason for your first problem is that you are returning the result of the call to ko.computed() and that is a function. Typically you would define a computed which is dependent on other observables and evaluate it via executing it:

var observable = ko.observable(); // this returns a function
var computed = ko.computed(function() { return observable; }); // this also returns a function
console.log(computed()); // logs undefined
observable('hello world'); // that call will update the computed
console.log(computed()); // logs hello world
console.log(computed); // this will log the function itself as in your exemple

The next problem is the binding of your click event handler. You bind data-bind="click: changeStoreHref(20)". When the HTML is parsed by ko it executes changeStoreHref(20) and binds against the result which is undefinded. And as a side effect, it already sets self.storeHref to 20.

If you have a scenario where you need to parametrize a click binding then you have to return a function:

HTML:

<a data-bind="attr: {href: link}">Link</a>

<a href="#" data-bind="click: changeStoreHref('test')">change Link</a>

JavaScript:

function viewModel() {
    var self = this;
    self.storeHref = ko.observable(1);

    self.link = ko.computed(function () {
        return '#someHash/' + self.storeHref();
    });

    self.changeStoreHref = function (para) {
        return function () {
            self.storeHref(para);
        }
    };
}

ko.applyBindings(new viewModel());

Fiddle: http://jsfiddle.net/dfLaK/1/