1

I have a custom KO component address-input

ko.components.register('address-input', {
    viewModel: { createViewModel: function ({}, componentInfo) {
        var self = {};
        self.dispose = function() {
            // When removed by KO, dispose computeds and subscriptions
        };

        return self;
    }},
    template: 'address-input'
});

The corresponding template is address-input.html

<div  class`enter code here`="clearfix row">
    <!-- elements come here -->
</div>

My application is an SPA one whose basic layout will be like below

A main.html will contain section.html which inturn holds address-input,html. On page nav , section.html will be replaced by another html and so on.

The section htmls are loaded through AJAX

        $j.ajax({
            url: url,
            success: function(htmlText) {
                var $el = $j(element);
                $el.html(htmlText);
                ko.applyBindingsToDescendants(bindingContext, $el[0]);
            },
            cache: false,
            mimeType: 'text/html-ko'
        });

I might have some observables subscribed in the address-input component in future. When that happens i would like the dispose method called when navigating away from the page. But it is not happening now. What is wrong here? Is it a case of DOM not getting removed from memory? If it is so , why?

Vivek
  • 341
  • 1
  • 5
  • 15

1 Answers1

5

You're using jQuery to replace a part of the DOM tree. Knockout has no way of knowing which elements are removed and cannot call dispose on the bound models.

Use knockout's html binding to add/remove the new section or (not recommended) call ko.cleanNode(element) before calling $el.html.

An example that shows:

  • When you manually remove a component from the DOM, knockout isn't notified and cannot call dispose
  • When you use a regular binding to alter the DOM (e.g. foreach, if, with) knockout does call dispose when stuff has to be removed
  • When you call ko.cleanNode, knockout detaches all nodes from their models, calls dispose, and let's you do what you want with the remaining DOM nodes.

ko.components.register('mycomponent', {
    viewModel: function(params) {
      this.dispose = () => console.log("Dispose called");
    },
    template: "<li>My Component</li>"
});
 
// Some example data to render a list
const comps = ko.observableArray([1, 2, 3, 4]);

// Remove straigt from the DOM without knockout...
const badRemove = () => document
  .querySelector("mycomponent:last-child")
  .remove();
 
const manualDetach = () => ko.cleanNode(document.querySelector("div"));
  
// Use knockout to alter the DOM
const goodRemove = () => comps.shift();

ko.applyBindings({ comps, badRemove, goodRemove, manualDetach });
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>

<div data-bind="foreach: comps">
  <mycomponent></mycomponent>
</div>

<button data-bind="click: badRemove">bad remove</button>
<button data-bind="click: goodRemove">good remove</button>
<button data-bind="click: manualDetach">clean node</button>
user3297291
  • 22,592
  • 4
  • 29
  • 45
  • 1
    Note that [`cleanNode` is undocumented and intended for internal use](https://stackoverflow.com/a/15069509/392102), so you probably shouldn't recommend it. – Roy J Jan 29 '18 at 20:43