Sunday, April 6, 2014

Rebinding a knockout view model

As you might have noticed reading my last two posts, I have been doing a bit of front-end work using knockout.js. Here is something that had me scratching my head for a little while..

In one of our pages we're subscribing to a specific event. As soon as that event arrives, we need to reinitialize the model that is bound to our container element. Going through snippets earlier, I remembered seeing the cleanNode function being used a few times - which I thought would remove all knockout data and event handlers from an element. I used this function to clean the element the view model was bound to, for then to reapply the bindings to that same element.

This seemed to work fine, until I used a foreach binding. If you look at the snippet below, what is the result you would expect?
<div id="books">
    <ul data-bind="foreach: booksImReading">
        <li data-bind="text: name"></li>
    </ul>
</div>

var bookModel = {
    booksImReading: [
        { name: "Effective Akka" }, 
        { name: "Node.js the Right Way" }]
};
                         
ko.applyBindings(bookModel, el);

var bookModel2 = {
    booksImReading: [
        { name: "SQL Performance Explained" },
        { name: "Code Connected" }]
};

ko.cleanNode(books);
ko.applyBindings(bookModel2, books);
Two list-items? One for "SQL Performance Explained" and one for "Code Connected"? That's what I would expect too. The actual result shows two list-items for "SQL Performance Explained" and two for "Code Connected" - four in total. The cleanNode function is apparently not cleaning the foreach binding completely.

Looking for documentation on the cleanNode function, I couldn't find any. What I did find was a year old Stackoverflow answer advising against using this function - since it's intended for internal use only.

I ended up making the book model itself an observable. The element is now being bound to a parent model that contains my original book model as an observable. When the event arrives now, I create a new book model and set it to that observable property. This results in my list being rerendered with just two items - like expected.
<div id="books">
    <ul data-bind="foreach: bookModel().booksImReading">
        <li data-bind="text: name"></li>
    </ul>
</div>

var page = {
    bookModel : ko.observable({
        booksImReading: [
            { name: "Effective Akka" }, 
            { name: "Node.js the Right Way" }]
    })
};
                          
ko.applyBindings(page, el);

page.bookModel({
    booksImReading: [
        { name: "SQL Performance Explained" },
        { name: "Code Connected" }]
});
Don't use the cleanNode function to rebind a model - instead make the model an observable too.

5 comments:

  1. the knockout docs are good but lack anything regarding this topic. found this post useful to solve similar issue (was with a nested object that seemed to stay bound to the previous object)

    ReplyDelete
  2. Thanks, this really helped. Looked a bit more and came across this fiddle which deals with the issue. http://jsfiddle.net/madcapnmckay/EYueU/

    ReplyDelete