1
votes

I have a viewmodel that's a created by a constructor function, with a bunch of observable properties and a bunch of plain old properties. Once I instantiate it, if I set a value on the instance, the change to that value doesn't get reflected in the computed observable.

Here's a distilled version of what I'm looking at:

function ViewModel(active) {
    var self = this;
    self.active = active;

    self.getClasses = ko.computed(function () {
        return this.active ? "yup" : "nope";
    }, self);
}

var vm = new ViewModel(false);
vm.active = true;

alert(vm.getClasses()); //returns "nope" :/

This computed observable will re-evaluate if I touch an observable it depends on, but calling it directly results in evaluation with the old value of active.

Does the ko.computed create a new closure that ignores updates to the parent? Is it ill-advised to mix plain values with observable ones? (The computed I'm actually having a problem with has dependencies on observables and on other properties, but I don't expect the other properties to change at runtime. This is only actually a problem in my unit tests right now.)

I can certainly make active an observable, but I'm wondering if there's another way to do this.

3
Computed values are only changed if in the computed's function an observable property changed. If there is no observable change then the computed won't be reevaluated... so you need to make self.active = ko.observable(active); or don't use a computed here but a regular function: self.getClasses = function () { return self.active ? "yup" : "nope"; }; For further read: knockoutjs.com/documentation/computedObservables.html How dependency tracking works section - nemesv
Again, I'm aware that making active an observable will result in the computed observable updating. The problem is that I want it to update when I invoke it, not simply when a dependency changes. - Josh Schultz
This is not how ko.computed works. The evaluator function only invoked intially and only if a dependent observable changed. You cannot tell a computed to updated its value. So don't use computed here. - nemesv
Why not rewrite your getClasses to be just a regular function? Then you would get the most current value of active. What's the point of making it a knockout computed function if you're not going to have it rely on other observables? - Tombala
@Tombala This is a simplified example. In reality, I have one computed observable that builds a value from a few properties, a few of which are observable. I'm trying to minimize the number of observables involved, making them only for things my UI needs to react to. It's not going as well as I'd hoped. :) - Josh Schultz

3 Answers

3
votes

Josh, internally a ko.computed executes your calculation function immediately and stores the result until some action requires it to re-calculate itself (like a subscribed-to observable changing). This internal caching potentially offers a huge performance boost; no need to re-run the calculation if no subscribed-to values have changed because the result should be the same.

This is why it's important that any values on which your calculation depends are created as observables.

There's currently no way to force a computed to re-calculate on demand, since generally that would indicate a problem in your architecture. If you need a value which is calculated on demand but has no dependencies on any observables, then a regular function would be the best way to achieve that.

self.getClasses = function () {
      return self.active() ? "yup" : "nope";
  };

You can bind your UI to display the result of regular functions just the same as to a computed, although you'll need to add parenthesis in your binding expression. Keep in mind that this will only display the result of the function at the time of binding. The UI will not be kept up to date if your calculation changes; for that you'd need a ko.computed.

<!-- this will only show the value at bind-time and never be updated -->
<div data-bind="text: getClasses()"></div>

As you can see, this isn't very useful. Overall, a few more observables aren't likely to impact your apps performance by more than a few microseconds. My recommendation is to hold off on optimizing your code until you see a problem. Your users are far more likely to see a slow down in loop evaluation or large AJAX payloads than in a few observables that make your code easier to write.

I hope this helps!

1
votes

Have you tried setting the observable values like this:

function ViewModel(active) {
  var self = this;

  self.active = ko.observable(active);

  self.getClasses = ko.computed(function () {
      return this.active() ? "yup" : "nope";
  }, self);
}

var vm = new ViewModel(false);
vm.active(true);

So setting the observable as a function (with the value between the brackets), rather than like a property. And making sure that active is defined as an observable.

0
votes

Active has to be an observable if you want the changes on this variable to be notified (and evaluated in a computed).