Pietro Coelho
Pietro Coelho

Reputation: 2072

Durandal - Share an observable across viewmodels

I'm trying to update an observable of viewmodel A inside viewmodelB

This is viewmodel A:

define(['knockout'], function(ko) {
 return {
  title: ko.observable('')
 }
})

And viewmodel B:

define(['knockout', 'viewmodelA'], function(ko, viewmodelA) {
 function vm() {

  this.changeName = function() {
   viewmodelA.title('test')
  }

}

return vm

})

Inside the viewmodelA html, there's a <p data-bind="text: title"></p> tag, and the changeName function is hooked into an click binding on the html of viewmodelB.

However, when I execute the changeName function, the viewmodelA.title() observable does change it's value, but the html is not updated.

What am I missing?

Upvotes: 0

Views: 733

Answers (1)

user3174746
user3174746

Reputation:

When communicating between views, it's best not to do so by injecting one view into another, lest you tightly couple one view to another and lock out other parts of your application.

Durandal comes with a pub/sub feature. Simply require 'durandal/app', and then use app.trigger() and app.on() to publish and subscribe, respectively.

In viewmodelA.js, we would have the following:

define(['durandal/app', 'knockout'], function(app, ko) {
    var          
        title = ko.observable(''),
        compositionComplete = function () {
            app.on('title:changed').then( function(aTitle) {
                title(aTitle);
            });
        },
        detached = function () {
            app.off('title:changed');
        };

    return {
        compositionComplete: compositionComplete,
        detached: detached,
        title: title
    }
});

In viewmodelB.js, we would have:

define(['durandal/app', 'knockout'], function(app, ko) {
    var             
        changeTitle = function (aTitle) {
            app.trigger('title:changed', aTitle);
        };

    return {
        changeTitle: changeTitle
    }
});

I'm using a singleton pattern, as you are, along with the Revealing Module Pattern to expose members to the outside world.

Essentially, what's happening is that, whenever changeTitle is called in viewmodelB with the new title (as string) as a param, it broadcasts the change as a global event, carrying the new title as a payload. In turn, viewmodelA expresses its interest in the 'title:changed' event by subscribing to that global event, and then responds by changing the title.

To test this, you just need a small test harness (perhaps a button you click, or you could even hard-code a test value for now) to call changeTitle in viewmodelB the a payload that carries a string representing the new title.

By taking the approach above, you completely decouple the views, and you allow other views or parts of your application to respond to the 'title:changed' event, as well.

If you need finer-grained control than Durandal's pub/sub, you could always use PostalJS, which is a full-blown client-side message bus in the AMQP style. That's what we use.

Upvotes: 3

Related Questions