14

If you have several view models on one page, how do you ensure that you can keep them synced? For example, if one item is added or a button clicked on one view model and you want the other view model to be sensitive to that change, can Knockout manage this natively or is it better to use some messaging or pub/sub architecture.

I want to stay away from having to manage observables between models.

Sanchitos
  • 8,423
  • 6
  • 52
  • 52
jaffa
  • 26,770
  • 50
  • 178
  • 289

4 Answers4

26

Knockout 2.0 does include functionality that lets you do basic pub/sub. Here is a sample where two view models communicate through a mediator.

var postbox = new ko.subscribable();

var ViewModelOne = function() {
    this.items = ko.observableArray(["one", "two", "three"]);
    this.selectedItem = ko.observable();
    this.selectedItem.subscribe(function(newValue) {
        postbox.notifySubscribers(newValue, "selected");
    });
};

var ViewModelTwo = function() {
    this.content = ko.observable();
    postbox.subscribe(function(newValue) {
        this.content(newValue + " content");
    }, this, "selected");
};

ko.applyBindings(new ViewModelOne(), document.getElementById("choices"));
ko.applyBindings(new ViewModelTwo(), document.getElementById("content"));

The first view model notifies through the postbox on a specific topic and the second view model subscribes to that topic. They have no direct dependency on each other.

Certainly the postbox would not need to be global and could be passed into the view model constructor functions or just created inside a self-executing function.

Sample: http://jsfiddle.net/rniemeyer/z7KgM/

Also, the postbox could just be a ko.observable (which includes the ko.subscribable functions).

RP Niemeyer
  • 114,592
  • 18
  • 291
  • 211
  • @RP Niemeyer - What is the difference between ko.subscribable and observable? – Ryan May 17 '12 at 14:47
  • 3
    ko.subscribable is a base "class" that observables and computed observables get functionality from. It provides the basic pub/sub features that both observables and computed observables use. – RP Niemeyer May 17 '12 at 15:28
  • Is it possible to get the return value from the method we are calling? Ex `var result=postbox.notifySubscribers(newValue, "methodReturnsValue")`. I have explained it in [this question](http://stackoverflow.com/questions/28828242/get-return-value-of-a-function-using-notifysubscribers) – Billa Mar 03 '15 at 11:59
  • JSFiddle throws `Cannot read property 'subscribe' of undefined` – JackTheKnife Aug 17 '18 at 18:21
  • @RPNiemeyer I have similar dilemma as the OP here https://stackoverflow.com/questions/51883917/knockoutjs-update-observable-from-another-modelview and get it solved with PubSub. Thoughts? – JackTheKnife Aug 17 '18 at 18:26
0

I have created a small extension to solve this problem for a recent project of mine. Slightly similar in methodology, but adds subscriptions to the published observable directly, and will queue up subscribers if declared prior to the declaration of a published observable.

Knockout PubSub

Erik Reedstrom
  • 627
  • 5
  • 3
0

The way I found to synchronising models is using the postbox library of RP Niemeyer

However I found something interesting regarding the observableArray. And that's why I created a new answer. Just to complete the answer from Niemeyer.

When using postbox and an observableArray the "subscribeTo" and "publishOn" events are fired when you add or remove an element from the observableArray. It doesn't fire anything when you update an element within the array. I think this doesn't have anything to do with the postbox library but a knockout limitation.

If you are attempting to get the event when updating an element of an observable array it's better to use "publish" and "subscribe" methods from the postbox library.

Please see the following FIDDLE

Code reference:

function FundEntity (fund)
{
    var self = this;
    self.id = fund.id;
    self.fundName = fund.fundName;
    self.description = fund.description;
    self.isFavorite = ko.observable(fund.isFavorite);
}


function GridViewModel(model) {
    var self = this;
    self.fundList = ko.observableArray();
    model.funds.forEach(function(fund) {
        self.fundList.push(new FundEntity(fund));
    }); 
    self.favorite = function (id, index) {
        var newValue = {
            id: id,
            index: index,
            isFavorite: self.fundList()[index].isFavorite()
        };
        ko.postbox.publish("itemChanged", newValue);
        return true;
    };
    self.isEditable = ko.observable().subscribeTo("myEditableTopic");
}

function FundDetailViewModel(model) {
    var self = this;
    self.fundList = ko.observableArray();
    model.funds.forEach(function(fund) {
        self.fundList.push(new FundEntity(fund));
    });    
    ko.postbox.subscribe("itemChanged", function (newValue) {        
        self.fundList()[newValue.index].isFavorite(newValue.isFavorite);        
    });
    self.editable = ko.observable(false).publishOn("myEditableTopic");
}
Community
  • 1
  • 1
Sanchitos
  • 8,423
  • 6
  • 52
  • 52
0

You seem to be driving toward contradictory goals. The way you would do that in Knockout is to create observables, but yet you don't seem to want that.

If you have Foo and Bar objects with observables, you may not want observables on Foo that interact with bar or vice-verse, but why not have a Widget that watches Foo and Bar and mediates?

Peter T. LaComb Jr.
  • 2,935
  • 2
  • 29
  • 44
  • What about the situation where you have a search box model on every page? You want to tell all other models to re-bind using the text in the search. Every page will have the search box model, but a different view model on each page. – jaffa Mar 27 '12 at 15:27