Search code examples
knockout.jsknockout-componentsknockout-postbox

Inter-component communication with KnockoutJS


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!


Solution

  • 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.