Thoughts on Knockout JS

I’m lazy and I think it’s high time I revived this blog so here goes. In the past six months at work, I’ve used Knockout JS in a couple of projects, including a (very) rich client app where I had the freedom to go nuts and develop it however I desired. Despite lack of experience in the area, I’d basically already made up my mind that I wanted to use some sort of templating framework and that I’d be mad not to, given that higher level declarative code is generally more succinct and maintainable than reams of low level spidery DOM manipulation. Without a huge amount of deliberation, I opted for Knockout.

I’ve found Knockout to be an intuitive framework which does a few key things and does them well. So here are some of my scatterbrained thoughts about it, coming from the perspective of someone whose previous JS framework experience included only jQuery, jQuery UI and Underscore. Won’t make much sense to anyone who hasn’t used it.

Overall architecture
Knockout JS is often described as an MVVM framework but really, it’s more like a VVM framework. The view model is a JS object consisting of KO observables, the view is HTML decorated with KO data-bind attributes etc., and the model is generally your own ‘stuff on the server’, so to speak. Commonly, changes made by users via the UI trigger AJAX GETs/POSTs from client to server. Most web apps have some sort of initial model which resides on the server and should be displayed to the user upon page load. KO doesn’t prescribe any specific method of transforming this server-side model into a KO view model, but it seems to me that there are at least a couple of ways:

1.) Do not render the initial model server-side… render nothing, and have your JS fetch the initial model upon page load via AJAX calls. This is elegant in the sense that all model reading/writing can be done client side in the same way via the same AJAX calls, whether it’s the initial view model load or anything which happens thereafter… the server does not require a view model and does not have to render anything. But this might not be realistic for large models where it will cause noticeable delay.

2.) Server-side render a representation of the model into a hidden field (say as JSON). Have your JS digest and interpret this model and create a view model accordingly. This might provide a better user experience for large complex models but of course requires you to write JS to translate the JSONified server-side model into a KO view model. A variation on this would be to construct a view model server side which is identical to your KO view model, and serialise it into a hidden field. Then on page load, the JS merely needs to deserialise this into an ‘initial view model’ without additional transformation.

3.) Server-side render the markup corresponding to the original model. *Somehow* reverse engineer it into a view model (essentially, do the opposite of what data binding does). Perhaps there are plugins out there for this?

In my app, I took the approach of delegating as much as possible to KO. That meant using built in bindings like click/checked etc. instead of their JS/jQuery counterparts, and writing KO bindings to wrap jQuery Sortable/Draggable etc., leaving very little raw jQuery event-handling code. Given my skepticism around the mixing of frameworks, I was initially worried that I’d hit a brick wall, but it turned out fine in the end. If I were to do things again though, I’d most likely handle mouse events outside KO. I’m not sure what most people tend to do here…

Unlike some other frameworks, KO does not impose any sort of code organisation/structure. While I prefer this flexibility, one of my colleagues was a bit apprehensive about it. And understandably so, as one can see that it would be quite easy to write some very messy and unnecessarily complex view models. However, there are KO plugins out there which deal with this sort of thing. I chose a very OOP-like approach when writing my view model, with my types (main app.js, view model, helpers) defined across many JS files using the module pattern.

Generally KO’s performance tax was never an issue for me, but one place where it did become an issue was with KO templates. I ran into an issue where KO would over-zealously re-render an entire collection of objects simply because an unimportant observable in one of them changed. It makes sense from KO’s point of view- it follows a simple rule which states that it should re-render something when anything it conceivably depends on has changed. But it’d be nice if you could control how KO does this. And since it’s easy to unintentionally create complex chains of dependencies, it’d be neat if KO provided a visualisation or debug dump of dependencies.

Composite Bindings
One obvious feature missing from KO is composite bindings. There were a few places in my markup where I would apply the same combination of five or so KO bindings with mostly the same arguments. This is problematic not only because it bloats markup, but also because these sets of identical bindings must be maintained in several different places in parallel. One slightly cludgey solution would be to abstract it away as a reusable HTML template. Another solution (which I attempted), was to roll my own custom binding which applied applies the bindings itself… this /almost/ worked but I had issues with one of the bindings which I was manipulating in hacky unsupported ways. The ideal solution would be a composite binding.

Computables vs Manual Subscriptions
One gotcha is that computed observables are often evaluated by Knockout itself earlier than you might expect. And if those observables rely on the view model being properly initialised prior to evaluation, this can lead to funny errors when it’s not. This and other things led to me sometimes converting computables into observables fuelled by manual subscriptions.

This entry was posted in JavaScript, Web and tagged , , , , . Bookmark the permalink.

2 Responses to Thoughts on Knockout JS

  1. Jeremy Cook says:

    Hi Nathan,
    I enjoyed reading your experiences and comparing them with my own.

    You commented on difficulties with converting to JSON, stating “I resolved this by nullifying these references prior to serialisation and restoring them afterwards… but of course this hacky strategy wouldn’t work if these references were observables, or were required during serialisation for other purposes.” Have you looked into implementing the toJSON function that returns a new object or value that is just the subset of your data you need?

    • Nathan Pitman says:

      Hi – I’m glad to hear that!

      Yes, I did see that blog post- in fact, I based my serialisation code on it and I did try pruning the unnecessary references in toJSON like Ryan suggested. But I still had performance issues so I concluded that it was the object graph creation (the ko.toJS part) which was slowing things down, and that led me to the above solution (nullifying the references prior to serialisation and restoring them afterwards).

      But what I’ve realised just now after writing up a jsFiddle test case, is that the performance problems are the result of a slight flaw in his implementation. The toJS call he’s adding is only needed if there’s the possibility that JSON.stringify() is going to be called on the view model (instead of ko.toJSON). The problem is that for view models like mine where subordinate objects have references to the main view model, it turns serialisation into an O(n^2) operation instead of O(n). I’ve just added a comment to his post to point that out and I’m going to remove the serialisation part from mine (since it’s not a KO issue).

Leave a Reply

Your email address will not be published. Required fields are marked *