Search code examples
javascriptknockout.jsknockout-3.0

Passing existing model to knockout component binding?


I've just began to explore knockout components, because our codebase was way before the components were introduced.

A few things I don't get at first read.

  1. How can one use an existing viewmodel at component binding?
  2. To what should I bind when calling applyBindings?

Here is a quick example of what I mean.

function Customer() {
  this.name = ko.observable();
  ...
  this.orders = ko.observableArray([]);
}
Customer.prototype.addOrder = function(order) {
  this.orders.push(order);
}
...
function Order() {
  this.date = ko.observable();
  ...
}
...
// HERE I want the component binding in the foreach to use the $data
ko.components.register("Customer", {
  viewModel: Customer,
  template: "<strong data-bind='text: name'></strong><ul data-bind='foreach: orders'><li data-bind='component: "Order"'></li></ul>"
});

ko.components.register("Order", {
  viewModel: Order,
  template: "<span data-bind='text: date'></span>"
});
...

<!-- HERE I would like the component binding to use $data too -->
<div data-bind="component: 'Customer'"></div> 

...

var customer  = new Customer(); 
customer.name = "Test";

var order = new Order();
order.data = new Date();
customer.addOrder(order);

ko.applyBindings(customer);

Solution

  • You can pass existing model to the component via parameters (params):

    function Customer(params) {
      this.name = ko.observable(params.name);
      this.orders = ko.observableArray(params.orders);
    }
    Customer.prototype.addOrder = function(order) {
      this.orders.push(order);
    }
    function Order(params) {
      this.date = ko.observable(params.date);
    }
    
    ko.components.register("customer", {
      viewModel: Customer,
      template: "<strong data-bind='text: name'></strong><ul data-bind='foreach: orders'><li><order params='date: date'></order></li></ul>"
    });
    
    ko.components.register("order", {
      viewModel: Order,
      template: "<span data-bind='text: date'></span>"
    });
    
    ko.applyBindings({ modelName: "Some Name", orders: [ { date: "01/01/01" } ] });
    <script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
    
    <customer params="name: modelName, orders: orders"></customer>

    Update: viewModel-less components spike (I like this idea of @Zoltán Tamási):

    function Customer() {
      this.name = ko.observable();
      this.orders = ko.observableArray([]);
    }
    Customer.prototype.addOrder = function(order) {
      this.orders.push(order);
    }
    function Order() {
      this.date = ko.observable();
    }
    ko.components.register("customer", {
      viewModel: function(params) { return params.model; },
      template: "<strong data-bind='text: name'></strong><ul data-bind='foreach: orders'><li><order params='model: $data'></order></li></ul>"
    });
    
    ko.components.register("order", {
      viewModel: function(params) { return params.model; },
      template: "<span data-bind='text: date'></span>"
    });
    
    var customer  = new Customer(); 
    customer.name = "Test";
    
    var order = new Order();
    order.date = new Date(Date.now());
    customer.addOrder(order);
    
    ko.applyBindings({ customer: customer });
    <script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
    
    <customer params="model: customer"></customer>