如果在返回模板之前删除元素,则Knockout组件将失败

问题描述:

我有一个KO自定义绑定,它为页面添加一个组件(作为虚拟元素,但我认为这不重要),然后将视图模型应用于它。该组件通过服务器的require来加载它的模板。

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).

这会导致竞争条件 - 如果在删除元素之前模板的异步查找尚未完成KO尝试应用它无法找到结束标记的组件并抛出错误。

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.

我想知道是否有任何人可以建议缓解这个问题?我已经知道 applyBindings 上没有回调机制,而且我没有认为 afterRenderCallback 将有助于它在到达目前之前发生错误。我想知道是否有办法取消,停止或中止流程但没有。

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.

任何想法?

这是小提琴,它展示了我的问题。

Here is a fiddle which demonstrates my problem.

My自定义绑定如下所示:

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);
  }

组件通过require加载:

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);

实际上我有一些更复杂的依赖关系,它们在初始化开始后将标志设置为false。 / p>

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

我想我找到了一个解决方案 - 多亏了这个发布 ...

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:

我的组件模板变为:

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

模板绑定将调用 onRendered 函数在它自我渲染后

vm 传入虚拟元素的 applyBindings 必须更改为传递该函数:

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 如下所示:

function onComponentRendered(){
    canDestroy(true);
}

我还需要更改 init 函数我需要包含一些新的observable,一个计算并更改导出的destroy函数:

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); };

然后销毁功能检查它是否能够并且应该在此之前销毁(注意它也可以整理自己)什么时候,现在代码被调用,我发现只有虚拟元素没有被删除所以需要jQuery的 nextUntil ...):

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()
  }
}

很好。