Knockout Computed Observable Not Updating after reading stored value

65 views Asked by At

I am using a Knockout computed observable to store data from my users in the following fashion:

var DummyClass = (function() {
   userPrefs = ko.observable({
      value1: 0,
      value2: 0,
      value3: 0});

   commonPrefs = ko.observable({
      required: false
   });

   var userSettings = ko.computed(function() {
      var value1 = userPrefs().value1;
      var value2 = userPrefs().value2;
      var value3 = userPrefs().value3;

      if (typeof value1 === 'undefined') {
         value1 = 0;
      }

      value1 = String(value1);

      if (value1.length === 0) {
         if (commonPrefs().required === true) {
            value2 = 0;
            value3 = 1;
         }
         else {
            value2 = 1;
            value3 = 0;
         }
      }

      return {
         value1: value1,
         value2: value2,
         value3: value3
      };
   }
}

If I retrieve value1 by using dummyClass.userSettings().value1 elsewhere in my code, or update it with new values, any further attempts to update the data stored in userSettings via dummyClass.userSettings({value1: 1,value2: 2,value3: 3}) (for example) will no longer work, and whatever values I have in userSettings will remain that way unless I reload my web application.

2

There are 2 answers

3
Brother Woodrow On

A couple of points:

  • Your userPrefs is observable, but its properties are not. So KO will not know whether they have been changed. One thing to remember with KO is you have to make everything explicitly observable. Though in this case, if you're always updating the entire object, you would get away with that.
  • A computed variable, by default, is not writable. You have to explicitly make it writable if that's what you want, but it's not something you would typically do (at least in all my years of working with KO I only ever once came across a valid use case for having a writable computed; of course, it might be a matter of personal preference!).
  • typeof value1 = 'undefined' is an assignment and not a comparison, so that won't work.

Here's some example code that does what you're looking for (I think):

function ViewModel() {
    var vm = this;
    
    vm.userPrefs = ko.observable({
        value1: 0,
        value2: 0,
        value3: 0
    });
    
    vm.commonPrefs = {
        required: false
    };
    
    vm.userSettings = ko.pureComputed({
        read () {
            var value1 = vm.userPrefs().value1 || 0;
            var value2 = vm.userPrefs().value2;
            var value3 = vm.userPrefs().value3;

            if (value1 === 0) {
                if (vm.commonPrefs.required === true) {
                    value2 = 0;
                    value3 = 1;
                } else {
                    value2 = 1;
                    value3 = 0;
                }
            }

            return {
                value1,
                value2,
                value3
            };
        },
        write(newVal) {
            vm.userPrefs(newVal);
        }
    });
}

Fiddle: https://jsfiddle.net/thebluenile/fqznpexg/

But the writable computed isn't really necessary, because you could just manipulate userPrefs directly: vm.userPrefs({ value1: 1, value2: 2, value3: 3}).

2
GoodboY On

You are trying to set a value to the computed field "userSettings", but you have only defined the "read" function (without arguments). In order to set some values via this property, you should define a "write" function. See computed writable docs.

Besides it, beware that if you have an observable field with a backing object (e.g. DummyClass.userPrefs), you should provide a new object to the "observable" function in order to update the field.

For example:

DummyClass.userPrefs().value1 = 1 - will not trigger an update for DummyClass.userPrefs

DummyClass.userPrefs({value1: 1, value2: 2, value3: 3}) - will trigger an update