Why does my ko computed observable not update bound UI elements when its value changes?
Asked Answered
F

2

17

I'm trying to wrap a cookie in a computed observable (which I'll later turn into a protectedObservable) and I'm having some problems with the computed observable. I was under the opinion that changes to the computed observable would be broadcast to any UI elements that have been bound to it.

I've created the following fiddle

JavaScript:

var viewModel = {};

// simulating a cookie store, this part isnt as important
var cookie = function () {  

    // simulating a value stored in cookies
    var privateZipcode = "12345";

    return {
        'write' : function (val) { privateZipcode = val; }, 
        'read': function () { return privateZipcode; }
    }
}();

viewModel.zipcode = ko.computed({
        read: function () {
            return cookie.read();
        },
        write: function (value) {
            cookie.write(value);
        },
        owner: viewModel
    });

ko.applyBindings(viewModel);?

HTML:

zipcode:
<input type='text' data-bind="value: zipcode"> <br />

zipcode: 
<span data-bind="text: zipcode"></span>?

I'm not using an observable to store privateZipcode since that's really just going to be in a cookie. I'm hoping that the ko.computed will provide the notifications and binding functionality that I need, though most of the examples I've seen with ko.computed end up using a ko.observable underneath the covers.

Shouldn't the act of writing the value to my computed observable signal the UI elements that are bound to its value? Shouldn't these just update?

Workaround

I've got a simple workaround where I just use a ko.observable along side of my cookie store and using that will trigger the required updates to my DOM elements but this seems completely unnecessary, unless ko.computed lacks the signaling / dependency type functionality that ko.observable has.

My workaround fiddle, you'll notice that the only thing that changes is that I added a seperateObservable that isn't used as a store, its only purpose is to signal to the UI that the underlying data has changed.

// simulating a cookie store, this part isnt as important
var cookie = function () {  

    // simulating a value stored in cookies
    var privateZipcode = "12345";

    // extra observable that isnt really used as a store, just to trigger updates to the UI
    var seperateObservable = ko.observable(privateZipcode);

    return {
        'write' : function (val) { 
            privateZipcode = val; 
            seperateObservable(val);
        }, 
        'read': function () { 
            seperateObservable();
            return privateZipcode; 
        }
    }
}();

This makes sense and works as I'd expect because viewModel.zipcode depends on seperateObservable and updates to that should (and does) signal the UI to update. What I don't understand, is why doesn't a call to the write function on my ko.computed signal the UI to update, since that element is bound to that ko.computed?

I suspected that I might have to use something in knockout to manually signal that my ko.computed has been updated, and I'm fine with that, that makes sense. I just haven't been able to find a way to accomplish that.

Faizabad answered 21/3, 2012 at 23:26 Comment(0)
F
18

sigh, I found someone with my exact same problem

If dependentObservables don't notifySubscribers on write, why do they even bother to do it on read? They get added to the observables list and subscribed to, but then they never trigger on updates. So what is the point of subscribing to them at all?

Ryan Niemeyer answers:

I think that for your scenario, dependentObservables may not be the right tool for the job. dependentObservables are set up to detect dependencies in the read function and re-evaluate/notify whenever any of those dependencies change. In a writeable dependentObservable, the write function is really just a place to intercept the write and allow you to set any observables necessary, such that your read function would return the proper value (write is typically the reverse of read in most cases, unless you are transforming the value).

For your case, I would personally use an observable to represent the value and then a manual subscription to that observable to update the original value (the one that you may not have control over).

It would be like: http://jsfiddle.net/rniemeyer/Nn5TH/

So it looks like this fiddle would be a solution

var viewModel = {};

// simulating a cookie store, this part isnt as important
var cookie = function () {  

    // simulating a value stored in cookies
    var privateZipcode = "12345";

    return {
        'write' : function (val) { 
            console.log("updated cookie value with: " + val);
            privateZipcode = val; 
        }, 
        'read': function () { 
            return privateZipcode; 
        }
    }
}();

viewModel.zipcode = ko.observable(cookie.read());

// manually update the cookie when the observable changes
viewModel.zipcode.subscribe(function(newValue) {
   cookie.write(newValue);   
});

ko.applyBindings(viewModel);​

That makes sense and its somewhat simpler to use. Overall I'm not sure how great of an idea it is to treat a cookie as an observable since the server could edit it in an ajax request, etc.

Faizabad answered 22/3, 2012 at 0:17 Comment(0)
D
0

Try making your internal privatezipcode an observable. See here: http://jsfiddle.net/KodeKreachor/fAGes/9/

Doy answered 21/3, 2012 at 23:42 Comment(4)
it's really writing / reading that value via $.cookie.get() and $.cookie.set(), simulated via cookie in my example. I don't have the ability to make that an observable, aside from what i'm trying to do in my ko.computed observable itself. The idea is that ko.computed would encapsulate the writing to / reading from the cookie, which is why I'm not a big fan of using subscribe.Faizabad
Right, but if your cookie object doesn't have anything intrinsic to it that the knockout dependency tracking system can track then you're probably out of luck. If you just make your internal privateZipcode an observable then you should be able to see the value update.Doy
So the cookie object itself isn't an observable, it only contains an observable so that it can be tracked when it is written to.Doy
Essentially you are suggesting the same thing that Niemeyer is, and I'll def +1 for that. He takes it a step farther (and this is what I was getting at) by basically saying "store it in an observable and subscribe to it". By doing that, it completely eliminates the need to have a ko.computed at all. That's the only difference in the two approaches.Faizabad

© 2022 - 2024 — McMap. All rights reserved.