0

From the viewmodel I want to update value to the server straight after it was changed in the view.

class OrderLine
{
    itemCode: KnockoutObservable<string>;
    itemName: KnockoutObservable<string>;

    constructor(code: string, name: string)
    {
        this.itemCode = ko.observable(code);
        this.itemName = ko.observable(code);
        //subscribers
        this.itemCode.subscribe(this.updateCode, this, "change");
        this.itemName.subscribe(this.updateName, this, "change");
    }

    updateCode = (newvalue: string) =>
    {
        //Update value to the server
    }

    updateName = (newvalue: string) =>
    {
        //Update value to the server
    }
}

Both values can be changed by user and with explicit subscriptions updating to the server/database works fine.

In the server side when updating itemCode will update value of itemName too. So response to the client will return json object with new value for itemName

Here i have a problem, because changing value of itemName in the viewmodel will fire a subscription callback method which will update same value to the server again

    updateCode = (newvalue: string) =>
    {
        //Update value to the server
        //in the handler of the successful request
        this.itemName(updatedvaluefromServer);   //-- this trigger another request   
    }

Question: is it possible to change value of KnockoutObservable which will notify only view subscribers?
Or is it possible to detect that value was changed from the view?

I tried used "sneaky update" by @RPNiemeyer from here https://stackoverflow.com/a/17984353/1565525
but this approach will suspend all subscribers from being notified, including view's subscribers

Community
  • 1
  • 1
Fabio
  • 31,528
  • 4
  • 33
  • 72
  • So I understand: the two variables are not independent, but you can update either one and the server should just update the other one? This kind of dependency suggests using a computed for one of them (either one) and make it writable. – Roy J Sep 10 '15 at 16:09

2 Answers2

1

Here's a pattern that utilizes a computed observable backed by a regular observable:

class OrderLine
{
    private _itemCode: KnockoutObservable<string>;
    private _itemName: KnockoutObservable<string>;
    itemCode: KnockoutComputed<string>;
    itemName: KnockoutComputed<string>;

    constructor(code: string, name: string)
    {
        this._itemCode = ko.observable(code);
        this._itemName = ko.observable(code);

        this.itemCode = ko.computed({
            read: () => this._itemCode(),
            write: (newValue) => {
                this._itemCode(newValue);
                // Update server
                // In the handler of the successful request:
                this._itemName("...");
            }
        });

        this.itemName = ko.computed({
            read: () => this._itemName(),
            write: (newValue) => {
                this._itemName(newValue);
                // Update server
            }
        });
    }
}

In the callback on succesful Ajax calls you update the backing observable, not the write in the computed, as to prevent the problem you're having.

Jeroen
  • 60,696
  • 40
  • 206
  • 339
0

Here's what I was talking about. code is a normal observable, and name is a writable computed. When code is updated, the read value of name will be updated. When name is written to, code is updated, which updates the read value of name. Only the update of the read value is a change in the observable value of name, so there are not two updates to name.

If you watch the console, you will see that updating either of the fields generates one update to each of them.

function orderLine(code, name) {
  return {
    code: code,
    name: name
  };
}
var serverValues = [
  orderLine(1, 'one'),
  orderLine(2, 'two'),
  orderLine(3, 'three')
];

function getNameFromCode(code) {
  var found = ko.utils.arrayFilter(serverValues, function(line) {
    return line.code == code;
  });
  if (found.length == 0) return '';
  return found[0].name;
}

function getCodeFromName(name) {
  var found = ko.utils.arrayFilter(serverValues, function(line) {
    return line.name == name;
  });
  if (found.length == 0) return '';
  return found[0].code;
}

function vm() {
  var self = {};
  self.code = ko.observable();
  self.name = ko.computed({
    read: function() {
      return getNameFromCode(self.code());
    },
    write: function(newValue) {
      console.debug("Writing code");
      self.code(getCodeFromName(newValue));
    }
  });

  self.code.subscribe(function(newValue) {
    console.debug("Updating code to:", newValue);
  });
  self.name.subscribe(function(newValue) {
    console.debug("Updating name to:", newValue);
  });

  return self;
}

ko.applyBindings(vm());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<label>Code</label>
<input data-bind="value:code" />
<br />
<label>Name</label>
<input data-bind="value:name" />
Roy J
  • 42,522
  • 10
  • 78
  • 102