Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Accessing a component viewmodel from its containing viewmodel

I'm experiencing with knockout.js components and require.js. This is working well so far, but I'm struggling with the following.

Let's say I have one instance of my component in a very simple html page :

<div id="exams">
    <databound-exam-control></databound-exam-control>
</div>

From the containing viewmodel :

require(['knockout', 'viewModel', 'domReady!'], function (ko, viewModel) {
    ko.components.register('databound-exam-control', {
        viewModel: { require: 'databound-exam-control-viewmodel' },
        template: { require: 'text!databound-exam-control-view.html' }
    });

    ko.applyBindings(new viewModel());
});

I would like to get the child viewmodel content to later save all the data of the page when I click a button.

For now I just trying to display the display the parent/child viewmodels in a pre tag :

<div>
    <pre data-bind="text: childViewModel()"></pre>
</div>

With the help of the containing viewmodel :

function childViewModel() {
        var model = ko.dataFor($('databound-exam-control').get(0).firstChild);
        return ko.toJSON(model, null, 2);
};

I get the following error during the call to ko.dataFor, probably because the the page is not completely rendered :

Cannot write a value to a ko.computed unless you specify a 'write' option. If you wish to read the current value, don't pass any parameters.

Is that it ? Or am I completelty missing the point here ?

Any help appreciated.

like image 661
Olivier MATROT Avatar asked Mar 10 '16 15:03

Olivier MATROT


1 Answers

The easier way to communicate between the parent viewmodel and the child component is by using parameters.

  • create a new observable property in the parent view model, like childViewModel = ko.observable()
  • pass it as parameter to the child component <databound-exam-control params= "{modelForParent: childViewModel}"> Note that the parameter inside the child viewmodel will be known as modelForParent, and in the parent view model will be known as childViewModel
  • in the viewmodel constructor for the component, in your databound-exam-control-viewmodel.js script, you receive the parameters as the only argument for your constructor. So, if your constructor looks like this: function SomeComponentViewModel(params) you can access the parameter as params.modelForParent
  • use the parameter to pass whichever information you need from the child componente to the parent component, for example: params.modelForParent(createdChildViewModel).

Now, the parent component can acces the child view model using the childViewModel observable.

Using observables is just one possibility. You can do it in other ways. For example pass a callback as parameter, and execute that callback in the viewmodel constructor to return whatever you want to the parent. I sometimes use this pattern:

  • create a register api callback in the parent, like this: registerApi = function(api) { config.childApi = api }
  • pass this as parameter to the child component
  • in the child view model constructor invoke the callback passing the child api

In this way, I can acces the api exposed by the child component like this: config.childApi.aMethosExposedByTheChild()

Important note: you must take into account that, as the child component loads asynchronously, the information exposed by the child is not immediately available.

Unless you need to use it immediately this isn't a problem. For example, in your case, it looks like you'll get information from the child viewmodel after the component has been loaded and the user has interacted with it, so that's not a problem.

If you need to access it as soon as possible you can use polling --- or better, expose a deferred (an example implementation: $.Deferred) to the child so that it can resolve it to let the parent veiw model know it's already available. This also happens when the child viewmodel depends on loading external resources by AJAX calls (for example to load a drop down list, or some other information existing on the server).

Another option, which I don't like, is that the parent viewmodel includes the whole child view model and passes it as parameter, so the parent viewmodel has full control on the child view model. Obviously this solution doesn't allow the component to be responsible for its own viewmodel, so there is a tight coupling between parent viewmodel and child component, which is not desirable.

like image 55
JotaBe Avatar answered Sep 18 '22 17:09

JotaBe