Search code examples
knockout.jstypescriptknockout-mapping-plugin

Observable subscriber of properties stop working after remapping object by mapping plugin


ViewModel initialized and mapped from JSON inside $(document).ready(..)

$(document).ready(function ()
{
    viewmodel = new Order();
    var data = { json data };
    ko.mapping.fromJSON(data, {}, viewmodel);
    ko.applyBindings(viewmodel);
});

class Order
{
    ID: KnockoutObservable<number>;
    InvoiceAddress: KnockoutObservable<Address>;
    constructor()
    {
        this.ID = ko.observable(0);
        this.InvoiceAddress= ko.observable(new Address());
        this.InvoideAddress.subscribe(function(newvalue) { console.log(newvalue)}, this);
    }
}

Property InvoiceAddress of another type Address can be null When user create or delete InvoiceAddress, $ajax request will be send and new JSON object returned which contain updated data of whole viewmodel. Received data mapped to the same instance of viewmodel

ko.mapping.fromJSON(data, {}, viewmodel);

If state of InvoiceAddress was changed from null to existed object, then binding for InvoiceAddress stops working.

<input data-bind="value: InvoiceAddress().StreetName">

Question: Is it possible to keep binding working after "re-mapping"?

Update: "Stops working" -> subscribers of properties in the "InvoiceAddress" variable not firing after "InvoiceAddress" become null and remapped back to normal object


Solution

  • Don't do this.

    <input data-bind="value: InvoiceAddress().StreetName">
    

    Do this:

    <div data-bind="with: InvoiceAddress">
        <input data-bind="value: StreetName">
    </div>
    

    Compare:

    ko.applyBindings({
        InvoiceAddress: ko.observable({StreetName: "Invoice Street"}),
        ShippingAddress: ko.observable(null),
        addShipping: function () { this.ShippingAddress({StreetName: "Shipping Street"}); },
        removeShipping: function () { this.ShippingAddress(null); }
    });
    <script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
    
    <b>Invoice Address</b>
    <div data-bind="with: InvoiceAddress">
        <input data-bind="value: StreetName">
    </div>
    
    <b>Shipping Address</b>
    <div data-bind="ifnot: ShippingAddress">-</div>
    <div data-bind="with: ShippingAddress">
        <input data-bind="value: StreetName">
    </div>
    
    <button data-bind="click: addShipping">Add Shipping</button>
    <button data-bind="click: removeShipping">Remove Shipping</button>

    Using with also makes it easier to turn the whole thing into a template or a component.