78

I've just upgraded to Knockout.js 2.3.0 and now I'm getting this error:

You cannot apply bindings multiple times to the same element.

which I wasn't getting in 2.2.1.

I'm getting a partial view from my MVC controller and adding it to the page after clicking on an href. The error happens the second time I click on the link to get the partial view. I'm doing this multiple times.

Is there a way to clear this out and avoid the new error thrown?

Here's my code:

$.get(url + "GetAssignedCompaniesView?layoutId=" + layoutId + "&noCache=" + new Date().getMilliseconds(), function (result) {
    $("#editAssignedPartial").html($(result));
    showEditAssignedArea(true);
    $(window.document).ready(function () {
        // error is thrown here
        ko.applyBindings(self, window.document.getElementById("editAssigned"));
        $("#layoutId").attr("value", layoutId);
        updateTypeHiddenElement.attr("value", "companies");
    });
});

Here's my HTML:

<div id="area1">
    <!-- grid here with links -->
</div>
<div id="editAssignedPartial"></div>
$(document).ready(function () {
    'use strict';
    var vm = new Vm();
    ko.applyBindings(vm, document.getElementById("area1"));
});
AlignedDev
  • 8,102
  • 9
  • 56
  • 91

13 Answers13

101

You need to remove the bindings before you use applyBindings again:

ko.cleanNode($element[0]);
vprasad
  • 1,451
  • 3
  • 13
  • 17
  • what I'm doing: I create a custom binding, and inside I dynamically bind a variable to an element. See: http://stackoverflow.com/a/9370953/809536 – ZiglioUK Sep 18 '13 at 21:58
  • New question: http://stackoverflow.com/questions/18883556/knockoutjs-2-3-custom-binding-ko-applybindings-to-partial-view-throws-err – ZiglioUK Sep 18 '13 at 22:48
  • this worked for me `var _data = $("view-data"); ko.cleanNode(_data);` – Gbolahan Dec 10 '13 at 09:55
  • 13
    `ko.cleanNode()` does not appear to be a recommended way to avoid the 'apply bindings multiple times' error. http://stackoverflow.com/a/15069509/538962 – mg1075 Jan 03 '14 at 16:06
  • can't get it to work with $('view-data') or $('#knockoutDiv')[0].. it keeps giving the applybindings error. I added the cleanNode call right before applying bindings. – Sonic Soul Apr 08 '14 at 20:18
  • It doesn't work for me,getting the error with single binding – user3040610 Sep 27 '16 at 13:36
32

Something that can happen as well which throws this exception is the following. Say you have:

ko.applyBindings(myViewModel1, document.getElementById('element1'));
...
ko.applyBindings(myViewModel2, document.getElementById('element2'));

Now, when both #element1 and #element2 don't exist you'll get the error. The reason is that Knockout's applyBindings falls back on document.body as root element when #element1 and #element2 are not found. Now it tries to apply binding twice on the body...

Not a nice fallback of Knockout if you ask me. I'd rather have a clear error message that the element does not exist in the DOM (yet).

Almer
  • 1,139
  • 1
  • 12
  • 14
  • Solved my issue. I was doing `document.getElementById('#element1')`. Forgot that the # is a jquery thing. It would indeed be much better if knockout would raise a 'element does not exist in the DOM' error. – jkokorian Jan 09 '16 at 11:36
  • I have a race condition? that behaves differently Chrome/FF/IE. My solution was to wrap it in a try-catch which removed the symptoms and got the page working. After reading this answer I also checked if the control existed before applyBindings and can see a difference between the browsers. – LosManos Apr 19 '16 at 13:07
  • That was my issue, I was trying to run Jasmine unit tests and wondering why this error came up (the elements the viewmodels are attached to weren't included in the test html) – kiwicomb123 Dec 23 '17 at 20:55
13

You should never apply bindings more than once to a view. In 2.2, the behaviour was undefined, but still unsupported. In 2.3, it now correctly shows an error. When using knockout, the goal is to apply bindings once to your view(s) on the page, then use changes to observables on your viewmodel to change the appearance and behaviour of your view(s) on your page.

x0n
  • 51,312
  • 7
  • 89
  • 111
  • The editAssignedPartial is dynamically added to the page, in a different event other than the page load. If I remove the applyBidndings from my click event, it doesn't show up. Is there a better way to do the dynamic addition? – AlignedDev Jul 18 '13 at 12:54
  • 21
    Sometimes it makes sense to bind a VM to a view more than once. For example, in a UI with a nav bar and a div to display the content of each clickable navbar element, you can either have X amount of divs for X navbar elements or just have one div and bind the VMs to that div based on what element is clicked. – vprasad Jul 18 '13 at 21:26
  • 3
    I ran into the same issue. I have dynamic content coming in that I need to rebind. – Mike Flynn Jul 18 '13 at 22:01
  • 2
    Another approach is to use templates. You can dynamically add a template `` to a page to control dynamic content. – x0n Jul 19 '13 at 20:19
  • I like the idea of using templates instead of the cleanNode approach. Thanks for that suggestion. – AlignedDev Jul 25 '13 at 14:41
  • I ran into this issue for requiring multiple bindings. For example binding to Kendo UI Grids that have buttons on them. Reference http://stackoverflow.com/questions/16342179/unable-to-format-date-with-kendoui-grid-and-knockout-kendo – Mr. Young Jul 26 '13 at 23:00
  • Knockout most certainly does support applying bindings more that once to a page, as long as they are applied to different elements. As mentioned below, if the scope element does not exist, or if one contains the other, or if one of your multiple `applyBindings` call is for the global scope, you will get an error: http://knockoutjs.com/documentation/observables.html – Spencer Williams Apr 28 '16 at 02:37
  • @SpencerWilliams Yes, I think that was implied in my answer but it seems not obvious to all. – x0n Apr 28 '16 at 15:45
  • But you said "the goal is to apply bindings once to your page". They do allow bindings to be applied more than once to a page, although to different elements. In saying "You should never apply bindings more than once to a view", you should clarify that by "view" you specifically mean "html element" and not "page". – Spencer Williams Apr 30 '16 at 01:26
  • @SpencerWilliams You're quite right. I'll edit my answer. – x0n Jun 16 '16 at 19:09
13

Two things are important for above solutions to work:

  1. When applying bindings, you need to specify scope (element) !!

  2. When clearing bindings, you must specify exactly same element used for scope.

Code is below

Markup

<div id="elt1" data-bind="with: data">
    <input type="text" data-bind="value: text1" >
</form>

Binding view

var myViewModel = {
  "data" : {
    "text1" : "bla bla"
  }
}:

Javascript

ko.applyBindings(myViewModel, document.getElementById('elt1'));

Clear bindings

ko.cleanNode(document.getElementById('elt1'));
Mitja Gustin
  • 1,723
  • 13
  • 17
  • I used this solution in a DevExtreme mobile app. Since DevX runs ko.applyBindings automatically I had to ko.cleanNode first then ko.applyBindings. Thanks Mitja! – Elim Garak Dec 19 '15 at 13:54
  • Why do you use jQuery selector in the `cleanNode()`? – Blaise May 25 '16 at 18:12
  • It's a jquery selector, but notation [0] gets the DOM object of jQuery object. I agree that this was not necessary and it's better to use either pure DOM object or jQuery object. – Mitja Gustin May 26 '16 at 08:01
  • @MitjaGustin why do people recommend against using ko.cleanNode? (and thanks this worked for me) – Tanya Gupta Oct 15 '17 at 17:34
8

There are a lot of great answers for this issue but I have a newbie answer.

I found that I accidentally added the same script in two places and it was trying to bind twice. So before you pull your hair out, make sure you check for this issue.

Pete
  • 2,393
  • 2
  • 24
  • 31
5

If you are reusing an element over and over (a Bootstrap modal dialog in my case), then calling ko.applyBindings(el) multiple times will cause this problem.

Instead just do it once like this:

if (!applied) {
    ko.applyBindings(el);
    applied = true;
}

Or like this:

var apply = function (viewModel, containerElement) {
    ko.applyBindings(viewModel, containerElement);
    apply = function() {}; // only allow this function to be called once.
}

PS: This might happen more often to you if you use the mapping plugin and convert your JSON data to observables.

Jess
  • 23,901
  • 21
  • 124
  • 145
5

I finally solved mine by returning { controlsDescendantBindings: true } in the init function of the binding handler. See this.

Martin Lottering
  • 1,624
  • 19
  • 31
2

Updated Answer

Now that we can use dataFor() to check if the binding has been applied, I would prefer check the data binding, rather than cleanNode() and applyBindings().

Like this:

var koNode = document.getElementById('formEdit');
var hasDataBinding = !!ko.dataFor(koNode);
console.log('has data binding', hasDataBinding);
if (!hasDataBinding) { ko.applyBindings(vm, koNode);}

Original Answer.

A lot of answers already!

First, let's say it is fairly common that we need to do the binding multiple times in a page. Say, I have a form inside the Bootstrap modal, which will be loaded again and again. Many of the form input have two-way binding.

I usually take the easy route: clearing the binding every time before the the binding.

var koNode = document.getElementById('formEdit');
ko.cleanNode(koNode);
ko.applyBindings(vm, koNode);

Just make sure here koNode is required, for, ko.cleanNode() requires a node element, even though we can omit it in ko.applyBinding(vm).

Blaise
  • 21,314
  • 28
  • 108
  • 169
  • Looks like using ko.cleanNode() is not recommendable since is used internally by Knockout and is not documentation for it. https://stackoverflow.com/questions/15063794/can-cleannode-be-used-to-clean-binding. So 'dataFor' looks safer to use. https://knockoutjs.com/documentation/unobtrusive-event-handling.html – darmis Dec 12 '19 at 09:46
1

I had this error occur for a different reason.

I created a template for save/cancel buttons that I wanted to appear at top and bottom of the page. It worked at first when I had my template defined inside of a <script type="text/html"> element.... but then I heard you could optionally create a template out of an ordinary <div> element instead.

(This worked better for me since I was using ASP.NET MVC and none of my @variableName Razor syntax was being executed at runtime from inside of the <script> element. So by changing to a <div> instead, I could still have the MVC Razor engine generate HTML inside my Knockout.js template when the page loads.)

After I changed my template to use a <div> instead of a <script> element, my code looked like this.... which worked fine on IE 10. However, later when I tested it on IE 8, it threw the error:

You cannot apply bindings multiple times to the same element

HTML:

<div id="mainKnockoutDiv" class="measurementsDivContents hourlyMeasurements">

  <div id="saveButtons_template" style="display: none;">
    ... my template content here ...
  </div>

  <!--ko template: { name: 'saveButtons_template' } -->
  <!--/ko-->

  Some_HTML_content_here....

  <!--ko template: { name: 'saveButtons_template' } -->
  <!--/ko-->

</div>

JavaScript:

ko.applyBindings(viewModel, document.getElementById('mainKnockoutDiv'));

THE SOLUTION:

All I had to do was move my saveButtons_template <div> down to the bottom, so that it was outside of the mainKnockoutDiv. This solved the problem for me.

I suppose Knockout.js was trying to bind my template <div> multiple times since it was located inside the applyBindings target area... and was not using the <script> element.... and was being referenced as a template.

Jason Parker
  • 4,960
  • 4
  • 41
  • 52
  • Yup. Moving my script include from before the target HTML to after the target HTML did the trick for me. – Griffin Oct 17 '14 at 16:58
1

I was getting the same error in IE 7/8. Worked fine in all other browsers including IE 9/10.

What I found worked for me was eliminating self closing tags.

Bad:

<div>
    <span data-bind="text: name"/>
</div>

Good:

<div>
    <span data-bind="text: name"></span>
</div>
Two Seeds
  • 53
  • 1
  • 5
1

In my case, I was adding to a non existing element, or, I was adding bindings to an element that maybe exists, but it's parent did not. Similar to this:

var segDiv =  $("#segments"); // did not exist, wrong id
var theDiv = segDiv.html("<div></div>");

ko.applyBindings(someVM, theDiv);

As far as I can tell, this error seems a bit overloaded in the sense that it will fire on a lot of different errors that can happen with the element, like it not existing. As such, the error description can be highly deceptive. It should have probably read:

Failure to bind bindings to element. Possible reasons include: multiple binding attempts, element not existing, element not in DOM hierarchy, quirks in browsers, etc
Neoraptor
  • 951
  • 1
  • 7
  • 13
0

I had the same problem and I solved it like this:

var vm = new MessagesViewModel()
ko.applyBindings(vm)

function ShowMessagesList() {
   vm.getData("MyParams")
}

setInterval(ShowMessagesList, 10000)
javad hemati
  • 236
  • 1
  • 2
  • 6
0
ko.cleanNode($("#modalPartialView")[0]);
ko.applyBindings(vm, $("#modalPartialView")[0]);

That works for me, but as others note, the cleanNode is an internal ko function, so there is probably a better way.

Muflix
  • 6,192
  • 17
  • 77
  • 153