2
votes

I'm using Knockout to bind an MVC view. This works fine the first time but for subsequent refreshes Knockout throws the error:

Error: You cannot apply bindings multiple times to the same element.

This is my binding code (inside Document.Ready), note that I am using setTimeout to run every 25 seconds, this is where the error occurs:

function viewModel() {
      Loading = ko.observable(true),
      CurrentUser = ko.observable(),
      Environments = ko.observableArray(),
      CurrentFormattedDate = ko.observable()
   }   

function doPoll() {
             $.get("/home/getindex")
                 .done(function (data) {
                     $(data).each(function (index, element) {
                         viewModel = data;
                         viewModel.Loading = false;
                         viewModel.CurrentFormattedDate = moment().format('MMMM YYYY');
                     });
                     ko.applyBindings(viewModel);                     
                 })
                 .always(function () {
                     setTimeout(doPoll, 25000);
                 })
                 .error(function (ex) {
                     alert("Error");
                 });
            };

            doPoll();

How do I avoid the error when DoPoll is called multiple times?

1
Why do you use $(data).each? You seem to never utilize the index/element arguments and just update the same object in a loop. The result would be equivalent to not using a loop at all. Also, I the viewModel object is wrong. You need to put this inside the member initialization, like this.Loading = ..., otherwise the supposed members will become global variables, and that is bad practice in JS!. - Ivaylo Slavov
Ah, good spot. The model was formerly bound to a collection and I have neglected to update the code! - Simon

1 Answers

1
votes

By default, bindings in Knockout may happen only once per dom element. The ko.aplyBindings would apply the binding to the document body, thus it will be already bound with data when you call it a second time from the doPoll function.

A possible solution is to make your current view model an observable property of a new view model; then only update the observable property:

var actualViewModel = {
    innerViewModel: ko.observable(new viewModel());
}   

function doPoll() {
    $.get("/home/getindex")
       .done(function (data) {
           $(data).each(function (index, element) {
                   data.Loading = false;
                   data.CurrentFormattedDate = moment().format('MMMM YYYY');
                   actualViewModel.innerViewModel(data);
               });                  
           })
       .always(function () {
                setTimeout(doPoll, 25000);
           })
       .error(function (ex) {
                alert("Error");
           });
 };

 doPoll();

You would need to call the initial binding against the new view model:

ko.applyBindings(actualViewModel);

You will also need to update the way properties are accessed in the bindings, by putting the innerViewModel in front - for instance:

<div data-bind="text: CurrentFormattedDate">...</div> 

would have to become

<div data-bind="text: innerViewModel.CurrentFormattedDate">...</div>