Josh Schultz
Josh Schultz

Reputation: 8190

Knockout: Computed Observable In Constructor Ignoring Changes To Instance

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.

Upvotes: 2

Views: 1929

Answers (3)

Ryan Rahlf
Ryan Rahlf

Reputation: 1802

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!

Upvotes: 4

Tom van Enckevort
Tom van Enckevort

Reputation: 4198

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.

Upvotes: 1

mael
mael

Reputation: 2254

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

Upvotes: 1

Related Questions