I've been using knockout heavily for a project recently and have come to really like it. This is a small to mid size website, about 6 engineer months of effort and somewhere in the mid 10klocs of custom javascript. Some strengths of knockout:
- it builds on the "Good Parts" of javascript, in that KO observables are just closures. You can pass them around or attach them to different objects however you feel like. For instance, we built a standard UI widget for entering currency values. Anywhere we need to accept currency, we instantiate one of these widgets and pass it the observable that holds the value. Clean, easy, encapsulated.
- Some people have a distaste for the "()" syntax: obj.prop() rather than obj.prop. There's some truth there, but the "()" syntax has one big benefit that overrides the slight ugliness in my experience: it catches naming errors immediately right at their source. If you've ever had to refactor a fairly big piece of JS, you know that one difficulty is correcting all the changed references. And if you miss one, the bug may be subtle, as the old reference doesn't except but quietly returns undefined. With knockout, the old reference blows up immediately, as undefined isn't a function. This is a big win.
We had some growing pains with knockout. Lessons learned:
- don't build anything but a small web app the way the knockout examples demonstrate. The reason is that while knockout claims to be MVVM, their examples are really just VVM -- there're no distinct model classes. They store data directly in the viewmodel classes. Bad things follow as your app grows: since the data is in the VM, any view that needs the data (i.e., all of them) needs to be bound to that same VM, meaning that your one VM ends up needing to feed several views. You find yourself annotating your viewmodel into sections -- "// these properties are used by view X ... // these properties are used by view Y" etc. Yech. Much better to have true model classes, then build a VM class on top of the model for each of your views.
- we now make heavy use of named templates to decompose large views into more manageable pieces. We wrote a simple custom binding to make this easy: since each VM is designed to work with a single view (see prior note), we put the #id of the view's named template into a .Template property of the VM class. The custom binding inspects this property and binds the VM class to its named template.
> - don't build anything but a small web app the way the knockout examples demonstrate.
Have you any code samples written like this you can share? I'm just about to start a large (for Knockout) project and it'd be great to start without making the same mistakes.
- it builds on the "Good Parts" of javascript, in that KO observables are just closures. You can pass them around or attach them to different objects however you feel like. For instance, we built a standard UI widget for entering currency values. Anywhere we need to accept currency, we instantiate one of these widgets and pass it the observable that holds the value. Clean, easy, encapsulated.
- Some people have a distaste for the "()" syntax: obj.prop() rather than obj.prop. There's some truth there, but the "()" syntax has one big benefit that overrides the slight ugliness in my experience: it catches naming errors immediately right at their source. If you've ever had to refactor a fairly big piece of JS, you know that one difficulty is correcting all the changed references. And if you miss one, the bug may be subtle, as the old reference doesn't except but quietly returns undefined. With knockout, the old reference blows up immediately, as undefined isn't a function. This is a big win.
We had some growing pains with knockout. Lessons learned:
- don't build anything but a small web app the way the knockout examples demonstrate. The reason is that while knockout claims to be MVVM, their examples are really just VVM -- there're no distinct model classes. They store data directly in the viewmodel classes. Bad things follow as your app grows: since the data is in the VM, any view that needs the data (i.e., all of them) needs to be bound to that same VM, meaning that your one VM ends up needing to feed several views. You find yourself annotating your viewmodel into sections -- "// these properties are used by view X ... // these properties are used by view Y" etc. Yech. Much better to have true model classes, then build a VM class on top of the model for each of your views.
- we now make heavy use of named templates to decompose large views into more manageable pieces. We wrote a simple custom binding to make this easy: since each VM is designed to work with a single view (see prior note), we put the #id of the view's named template into a .Template property of the VM class. The custom binding inspects this property and binds the VM class to its named template.