1

Say you have a product overview page with the following view models:

  • ProductIndexViewModel; the root view model that is bound to the entire page
    • ProductListViewModel; a widget for displaying all products
    • ProductRegistrationViewModel; a widget for registering new products

The widgets are loaded by using custom HTML elements (e.g. <product-list></product-list> and <product-registration></product-registration>). This is great because I don't have to put any knowledge of these widgets in my root-model. However, I would also like to refresh the product list after the user has registered a new product. In short:

How do I send a signal from ProductRegistrationViewModel to ProductListViewModel?

I've already looked into Knockout Postbox but this does not seem to solve the problem. What if I have multiple product lists and I only want to refresh one of them? Ideally, I would want to implement a series of public methods on my component's view model. Then tie the two together from my page's root view model, like this:

var ProductIndexViewModel = function() 
{
    var productRegistrationComponent = ??;
    var productListComponent = ??;

    productRegistrationComponent.onRegistrationComplete(function() {
      productListComponent.refresh();
    };
}

However, I don't have access to these view models from here. Or do I?

How can I access the child view-models from my root view model?

Finally, if anyone sees a better solution to my problem: I'm all ears!

Martin Devillers
  • 17,293
  • 5
  • 46
  • 88
  • I would have done it (and have done it) using a messagebus implementation (similar to the one you linked to above). If you you only want some product lists to refresh then it's likely a different business event triggered and therefore should be another event name. Another approach could be to depend on a common service or similar, which would provide the products. – Robert Westerlund Sep 10 '14 at 14:50
  • But the knowledge of the events is stored within the component. If I introduce a new event, I need to introduce a new component, thus losing my re-usability. Your second approach seems more feasible: rather than defining the datasource inside the component, I pass the datasource as a dependency with the component. – Martin Devillers Sep 10 '14 at 17:24

2 Answers2

3

It sounds like they should be sharing an observable array (the product list). I think it would be best to define this list in the root-model and pass it to the widgets that depend on it. If both components share the same observable, you don't have to worry about communicating between the two components. Your HTML could look something like this: <product-list data-bind="list: $root.allProducts"></product-list>.

How can I access the child view-models from my root view model?

If you want you could use ko.contextFor to get the context for a HTML element.

sroes
  • 14,663
  • 1
  • 53
  • 72
  • Hmm I was hoping I could keep all knowledge regarding the retrieval of products inside the component. In other words, the component has all knowledge to retrieve, transform and present products. Then again, if I separate the data-source from the component, I can instantiate the data-source in the root-model and pass it onto the components. – Martin Devillers Sep 10 '14 at 17:28
  • This way you can still keep that logic inside the component. The root is responsible for creating the needed list(s) (observable arrays), these can then be passed to your components. It's then up to that component to use that list, whether that's for populating, modifying or reading. – sroes Sep 10 '14 at 19:04
2

I've just recently done this sort of thing. In my approach, my component creates an API object that is assigned to an observable that is passed in by the parent component. For example, I have a datatable wrapped by a component which I would like to be able to find the selected rows:

pageModel = function() {
  this.dtAPI = ko.observable();
}

ko.applyBindigns(new pageModel());
<datatable params="api: dtAPI, data: getTableData()"></datatable>

Inside the component, you'd set the observable to the reference to the API for your component so that others can invoke calls to it. In my case, I'm not exactly doing this case, I expose an API to a databinding so that I can manipulate the databound element later (in this case it's a datatable binding:

if (binding.api != null)
{
    // expose datatable API to context's api binding.
    binding.api({
        getSelectedData: function() { return _getSelectedData(element);}
    });
}

Then in the main (parent) component, you can do things like:

dtAPI.getSelectedData();

So far, I've found this pattern as a way to expose behavior from an inner component useful.

nobody
  • 19,814
  • 17
  • 56
  • 77
Chris Knoll
  • 391
  • 2
  • 13