4

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);
Zoltán Tamási
  • 12,249
  • 8
  • 65
  • 93

4 Answers4

3

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> 
Community
  • 1
  • 1
TSV
  • 7,538
  • 1
  • 29
  • 37
  • 1
    I know about parameters. But I don't get why should I create a new instance of viewmodel everytime, instead of using the ones in `orders` for example. This doesn't seem to make sense. There are already prepared and ready `Order` instances sitting inside a `Customer` instance. Every logic tells me that they should be used at binding, otherwise it would kind of get out of sync too. – Zoltán Tamási Sep 14 '16 at 13:28
  • 1
    Also, in our current scenario we manually instantiate the whole viewmodel structure before applying bindings, so we have complete flexibility, for example to load initial data, etc. As far as I can see currently, using `component`s goes against this method as each component insists to create its own instance or viewmodel. – Zoltán Tamási Sep 14 '16 at 13:31
  • Just as an addition, I'm aware of `component`s without viewmodels, and I know I could use them and pass the existing viewmodel with `params`. But that smells quite badly. – Zoltán Tamási Sep 14 '16 at 13:33
  • You can pass entire model as a parameter for component. Components are using "viewModel" - to adopt your model for markup. If you don't need additional adapter for model bo be binded to markup, then you need not to create components - just use bindings (template binding). – TSV Sep 14 '16 at 13:35
  • 1
    Yes I understand that, although I like the idea of custom elements, AMD-style template loading, etc. So I'd like to make use of these features, but keep my existing viewmodel class structure. I would simply need a way to bypass viewModel creation of the component and pass my existing one. Maybe I'll go with viewModel-less components. – Zoltán Tamási Sep 14 '16 at 13:55
  • 1
    I like your idea of viewModel-less components and updated the answer with a spike. – TSV Sep 14 '16 at 14:08
  • Hm.. I like this trick, however I think there should be a built-in way to achieve this, because it still smells like a hack. I'll post on github for this, they definetely know things better. Thank you though, I'll accept your answer. – Zoltán Tamási Sep 14 '16 at 14:13
2

The solution you are looking for is to register your component with this view model factory:

ko.components.register("your-element", { 
    template: {...}, 
   { createViewModel: (params, componentInfo) => ko.dataFor(componentInfo.element) };
};

This will give you "pass through" binding, e.g.:

<div data-bind="with: SomeModel">
    <your-element></your-element>
</div>

The view model in the HTML loaded for your template will be SomeModel.

Evan Machusak
  • 694
  • 4
  • 9
0

Last question first: you still applyBindings to the viewmodel for the application. Using components doesn't change that.

Components can receive parameters. That is how you would pass anything to their viewmodel constructor.

Roy J
  • 42,522
  • 10
  • 78
  • 102
0

Have you tried using "A shared object instance" like here: http://knockoutjs.com/documentation/component-registration.html#specifying-a-viewmodel?