1

I have a KO custom binding which adds a component to the page (as a virtual element but i don't think this matters) and then applies a view model to it. The component loads it's template via require from the server.

However during load I am getting a problem where the custom binding is updated and removing the element from the page (I want to have it tidy it's self up if not required).

This results in a race condition - if the asychronous lookup for the template has not finished before the elements are removed when KO tries to apply the component it can't find the closing tag and throws an error.

I am wondering if there is any thing anyone can suggest to alleviate the problem? I already know there is no callback mechanism on applyBindings and I don't think afterRenderCallback will help as it errors before it gets that far. I wondered if there is a way to cancel, stop or abort the process and there isn't.

Any ideas?

Here is a fiddle which demonstrates my problem.

My custom binding looks like this:

ko.bindingHandlers.customBinding = {
    update: function(element, valueAccessor){
      var $element = $(element)

      if(ko.unwrap(valueAccessor())){
        $element.data("controller", new CustomBindingController(element));
      } else {
        var controller = $element.data("controller");

        if(controller){
          controller.destroy();

          $element.removeData("controller");
        }
      }
    }
  }

It is making calls to a controller class which looks like this:

function CustomBindingController(element){
    var self = this,
    $element = $(element),
    $component;

    function init(){
      $component = $("<!-- ko component: { name: \"my-component\", params: $data } --><!-- /ko -->");

      $("#component-container").append($component);

      ko.applyBindings( { message: "Binding Applied!" }, $component[0]);

      self.destroy = destroy;
    }

    function destroy(){
      $component.remove();
    }

    init.call(self);
  }

The component is loaded via require:

  ko.components.register("my-component", {
    //template: "<p data-bind=\"text: message\"></p>"
    template: { require: "text!component-template" }
  });

And a simplified initialisation looks something like this:

  var vm = { shouldBeBound: ko.observable(true) };

  ko.applyBindings(vm);

  vm.shouldBeBound(false);

In reality I have some more complex dependencies which are setting the flag to false after initialisation has begun.

Community
  • 1
  • 1
jaybeeuu
  • 1,013
  • 10
  • 25

1 Answers1

1

I think I have found a solution - thanks to this post...

Some modification of my components etc. allowed me to use the same workaround I have an updated fiddle and some sample of the modification:

My component template became this:

<p data-bind="text: message"></p>
<span display="none" data-bind="template: { afterRender: onRendered }"></span>

the template binding will call the onRendered function after it has rendered itself.

The vm passed into the virtual element's applyBindings had to change to pass that function in:

ko.applyBindings( { message: "Binding Applied!", onRendered: onComponentRendered }, $component[0]);

onComponentRendered looks like this:

function onComponentRendered(){
    canDestroy(true);
}

I also needed to change the init function i needed to include some new observables, a computed and to change the destroy function exported:

shouldDestroy = ko.observable(false);
canDestroy = ko.observable(false);

destroyComputed = ko.computed(destroy);

self.destroy = function(){ shouldDestroy(true); };

The destroy function then checks if it can and should destroy before doing so (notice it also tidies itself up when it does and now that the code is being called i found that only the virtual element was not being removed so jQuery's nextUntil was needed...):

function destroy(){
    if(shouldDestroy() && canDestroy()){
      var $template = $($component.get(0)).nextUntil($component.get(1));
      $component.remove();
      $template.remove();
      destroyComputed.dispose()
  }
}

Nice.

Community
  • 1
  • 1
jaybeeuu
  • 1,013
  • 10
  • 25