Skip to content

Custom Two Way bindings

Dmitry Panyushkin edited this page Jul 17, 2018 · 1 revision

If we want to write custom two-way binding that can work with @observable object property, we can use the following helper.

It wraps @observable object property to hidden writeable ko.pureComputed. And we can work with wrapped property like with regular ko.observable inside the binding.

Usage:

registerTwoWayBinding("valueWithDefault", {
  init(element, valueAccessor, allBindings, viewModel, bindingContext) {
    // `value` is always ko.observable
    const value = valueAccessor();
    value(element.value);
    ko.applyBindingsToNode(element, { value  }, context);
  },
  update(element, valueAccessor, allBindings, viewModel, bindingContext) {
    // ...
  }
});

class ViewModel {
  @observable property = "";
}

<input data-bind="valueWithDefault: property" value="Default Value" />

// now viewModel.property === "Default Value"

Helper:

export function registerTwoWayBinding(name, config) {
  ko.bindingHandlers[name] = {
    init: wrapBindingHandler(name, config.init),
    update: wrapBindingHandler(name, config.update)
  };
  ko.expressionRewriting["_twoWayBindings"][name] = true;
}

function wrapBindingHandler(name, bindingHandler) {
  if (!bindingHandler) {
    return bindingHandler;
  }
  return function(element, valueAccessor, allBindings, viewModel, bindingContext) {
    var value = valueAccessor();
    if (!ko.isObservable(value) && !ko.isWriteableObservable(value)) {
      var proxy = ko.pureComputed({
        read: valueAccessor,
        write: allBindings.get("_ko_property_writers")[name]
      });
      valueAccessor = function() {
        return proxy;
      };
    }
    bindingHandler(element, valueAccessor, allBindings, viewModel, bindingContext);
  };
}

Clone this wiki locally