Knockout How can I subscribe to another view model's properties?

671 views Asked by At

I am using knockout components to build up a search view with different partial views which has their own view models:

  • Search Field
  • Filter

So, the search-field view model looks something like this:

define(["knockout", "text!./search-field.html"], function (ko, templateMarkup) {
    function SearchFieldVM(params) {
        this.query = ko.observable("");
    }

    return { viewModel: SearchFieldVM, template: templateMarkup };
}

and the filter view model looks something like:

define(["knockout", "text!./filter.html"], function (ko, templateMarkup) {
    function FilterVM(params) {
        this.categories = ko.observableArray();
        this.currentCategory = ko.observable();
    }

    return { viewModel: FilterVM, templateMarkup };
}

I have then a Search VM:

define(["knockout", "text!./search.html"], function(ko, templateMarkup) {
    function SearchVM(params) {
        this.currentQuery = ko.observable();
        this.currentCategory = ko.observable();
    }

    return { viewModel: SearchVM, template: templateMarkup };
}

Ok, so here's the thing. Whenever one changes the query observable in SearchFieldVM I wish to change the currentQuery observable in SearchVM.

The same goes for currentCategory.

Let's say that my Search view looks like:

<search-field></search-field>
<filter></filter>

How can I then listen on the search-field component's query observable and filter component's currentCategory observable so that the SearchVM notices these changes?

1

There are 1 answers

0
Anish Patel On

It sounds like you want the "query" property on an instance of the "SearchFieldVM" class to reference the "currentQuery" property on an instance of the "SearchVM" class, i.e. when "query" is updated then "currentQuery" should update too.

You can achieve this by passing in a reference of an observable into the constructor of each view model using param like this:

define(["knockout", "text!./search-field.html"], function (ko, templateMarkup) {
    function SearchFieldVM(params) {
        this.query = params.refToSomeObservable;
    }

    return { viewModel: SearchFieldVM, template: templateMarkup };
}

define(["knockout", "text!./search.html"], function(ko, templateMarkup) {
    function SearchVM(params) {
        this.currentQuery = params.refToSomeObservable;
        this.currentCategory = ko.observable();
    }

    return { viewModel: SearchVM, template: templateMarkup };
}

Assuming you have some other view model representing the view that contains instances of these components, we can create a property on that to represent the this shared observable.

var MyViewModelThatRepresentsSomeContainerOfComponents = {
name: ko.observable("whats your name?")
searchQuery: ko.observable('default query maybe?')

}

Then in your markup you can do this:

<div>
    <input type="text" data-bind="value: name" />
    <search-field params="refToSomeObservable: searchQuery"></search-field>
    <filter></filter>
    <search params="refToSomeObservable: searchQuery"></search>
</div>

Otherwise, if the "SearchVM" is the container view model that nests the other two components within it then you can use the approach in this fiddle