knockout / knockout

Knockout makes it easier to create rich, responsive UIs with JavaScript
http://knockoutjs.com/
Other
10.45k stars 1.52k forks source link

Dynamically added component creates 2nd copy of view model. #2292

Open ghost opened 7 years ago

ghost commented 7 years ago

Let's say we have some code like this one, where "manage" is custom registered component:

var element = document.createElement("manage");
spot.appendChild(element);

var manage = new ViewModel();

ko.applyBindings(manage, element);

"manage" component has custom loader and overridden createViewModel method:

ko.components.register('manage', {
        template: { load: window.location.origin + '/components/template/manage' },
        viewModel: {
            createViewModel: function (params, componentInfo) {
                var view = new ViewModel();
                // Do some custom things on view model.
                return view;
            }
        },
        synchronous: false
    });

Two copies of "manage" object will be created: one is manually and one by createViewModel. How to access (inside createViewModel) the instance passed to applyBindings and return it instead of new one? It looks like it is something impossible...

I cannot call applyBindings without arguments. So in other words how to add new component dynamically?

codymullins commented 7 years ago

What version of Knockout are you on?

ghost commented 7 years ago

Knockout JavaScript library v3.4.2

ghost commented 7 years ago

I have solved it by passing null instance to applyBindings.

ko.applyBindings(null, element);

To get vm instance created in "createViewModel" I had to use "ko.dataFor". So all this looks very tricky. I will leave issue open for any comments or discussions.

kaheglar commented 7 years ago

Is this what you are looking for?

ko.components.register('manage', {
  template: { load: window.location.origin + '/components/template/manage' },
  viewModel: {
      createViewModel: function (params, componentInfo) {
          return ko.contextFor(componentInfo.element).$root;
      }
  },
  synchronous: false
});
ghost commented 7 years ago

@kaheglar

Yes, it is nice idea! But of course I need to check if root is available. It can be undefined in case of calling applyBindings without parameters. Something like this:

var view = root ? root : new SomeViewModel();

ghost commented 6 years ago

Unfortunately something like this cannot be used:

var root = ko.contextFor(componentInfo.element).$root;
var view = root ? root : new VM(componentInfo.element);

I will have the problem with nested components.
Nested component during creation will get context of parent component and its view-model.
So for nested components root will not be empty.

The single option is to compare element stored with model.. Somethig like this:

var view = root && root.element === componentInfo.element ? root : new VM(componentInfo.element);

In this case we have to expose element field outside of VM. But it is not wanted behaviour. I would to listen your comments guys.