0

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?

Pietro Coelho
  • 1,894
  • 1
  • 26
  • 34
  • How are you passing `viewmodelA` into `viewmodelB`? –  Feb 26 '15 at 20:36
  • viewmodelA is defined on main.js, i can get a reference to it with: define(['knockout', 'viewmodelA'], function(ko, viewmodelA) – Pietro Coelho Feb 26 '15 at 20:38
  • And what is the relationship between `viewmodelA` and `viewmodelB`? Parent-child? Sibling? Does `viewmodelB` have a corresponding view as well? –  Feb 26 '15 at 20:40
  • viewmodelB has a view which is composed inside shell.html. There is just one tag inside the view with "data-bind='text: title'". I need to change viewmodelA values from viewmodelB (viewmodelA is the interface for one audio player, viewmodelB is the view where you play songs, but now i'm just testing) – Pietro Coelho Feb 26 '15 at 20:44

1 Answers1

3

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.

  • There isn't other way to do that without using a thid-party library? It seems that if i need to listen to 20 observables the code will look like a mess – Pietro Coelho Feb 27 '15 at 13:11
  • @PietroCoelho As I indicated, you can use Durandal's built-in pub/sub. You don't have to go to a third-party library. So, the code I've provided is actually using Durandal's pub/sub. Switching to a third-part discussion, for the record, a well-organized use of a client-side message bus (such as PostalJS) actually makes for elegant, highly malleable code. With that said, if you just throw down publishers and subscribers with no architecture, then, yes, the code will be a mess. –  Feb 28 '15 at 05:20
  • I understand, but isn't the way I tried to access viewmodelA observable suppose to work? I've seem a lot of examples of requiring modules and then changing observable values, but none of them ever worked here. For example: http://stackoverflow.com/questions/20688713/durandal-showing-a-loading-during-composition I can' get this to work because even changing shell.showSplash inside another viewmodel, the observable doesn't get updated. – Pietro Coelho Feb 28 '15 at 15:24
  • @PietroCoelho You're trying to compose viewModelB with viewA, and, no, that isn't going to work. That's why the changing of the observable in the viewModel is working, but you're not seeing anything. The problem lies in the view, not in the viewModel. –  Feb 28 '15 at 16:54
  • I'm not sure if I understand.. i don't need viewmodelA to make viewmodelB work, but i'd like to access viewmodelA properties from viewmodelB. For example, if I create an instance of the viewmodel before returning it on the module and save it in a property of the window object, I can access this property anywhere on the code and any changes will reflect the viewmodelA view. I want something similar to that but without relying on window properties. – Pietro Coelho Mar 02 '15 at 18:26