Knockout - What MVVM should have been like in XAML
I've not got a lot of JavaScript programming experience, but I've been learning a bit recently, and decided to try knockout.js. I was pleasantly surprised at how quickly I was able to pick it up. In part this is because it follows a MVVM approach very similar to what I am used to with WPF. I've blogged a three-part MVVM tutorial here before as well as occasionally expressing my frustrations with MVVM. So I was interested to see what the MVVM story is like for Javascript developers. What I wasn't expecting, was to find that MVVM in JavaScript using knockout.js is a much nicer experience than I was used to with XAML + C#. In this post I'll run through a few reasons why I was so impressed with knockout.
Clean Data-Binding Syntax
The first impressive thing is how straightforward the binding syntax is. Any HTML element can have a data-bind
property attached to it, and that can hold a series of binding expressions. Here's a simple binding expression in knockout that puts text from your viewmodel into a div
:
<div data-bind="text: message" ></div>
To bind multiple properties, you can just add extra binding statements into the single data-bind
attribute as follows:
<button data-bind="click: prev, enable: enablePrev">
In XAML the syntax for basic bindings isn't too cumbersome, but a bit more repetitive nonetheless:
<TextBox Text="{Binding Message}"
Enabled="{Binding EnableEditMessage}" />
Event Binding
One frustration with data binding in XAML is that you can't bind a function directly to an event. So for example when an animation finishes, it would be great to be able to call into a method on our ViewModel with something like this:
<Storyboard Completed="{Binding OnFinished}" />
Sadly, that is invalid XAML, but with knockout.js, binding to any event is trivial:
<div data-bind="click: handleItem"/>
Arbitrary expressions in binding syntax
XAML data binding does allow you to drill down into members of properties on your ViewModel. For example, you can do this:
<TextBlock Text="{Binding CurrentUser.LastName}" />
And it also does give you the ability to do a bit of string formatting, albeit with a ridiculously hard to remember syntax:
<TextBlock Text="{Binding Path=OrderDate, StringFormat='{}{0:dd.MM.yyyy}'}" />
But you can't call into a method on your ViewModel, or write expressions like this
<Button IsEnabled="{Binding Items.Count > 0}" />
However, in knockout.js, we have the freedom to write arbitrary bits of JavaScript right there in our binding syntax. It's simple to understand, and it just works:
<button data-bind="click: next, enable: index() < questions.length -1">Next</button>
This is brilliant, and it keeps the ViewModel from having to do unnecessary work just to manipulate data into the exact type and format needed for the data binding expression. (Anyone who's done MVVM with XAML will be all too familiar with the task of of turning booleans into Visibility values using converters)
Property Changed Notifications
Obviously, knockout needs some way for the ViewModel to report that a property has changed, so that the view can update itself. In the world of XAML this is done via the INotifyPropertyChanged
interface, which is cumbersome to implement, even if you have a ViewModelBase class that you can ask to do the raising of the event for you.
private bool selected;
public bool Selected
{
get { return selected; }
set
{
if (selected != value)
{
selected = value;
OnPropertyChanged("Selected");
}
}
}
Contrast this with the gloriously straightforward knockout approach which uses a ko.observable:
this.selected = ko.observable(false);
Now selected
is a function that you call with no arguments to get the current value, and with a boolean argument to set its value. It's delightfully simple. I can't help but wonder if a similar idea could be somehow shoehorned into C#.
To be fair to the XAML MVVM world, you can alleviate some of the pain with Simon Cropp's superb Fody extension, which allows you to add in a compile time code weaver to automatically raise PropertyChanged events on your ViewModels. I use this on just about all my WPF projects now. It is a great timesaver and leaves your code looking a lot cleaner to boot. However, in my opinion, if you have to use code weaving its usually a sign that your language is lacking expressivity. I'd rather directly express myself in code.
Computed properties
Computed properties can be a pain in C# as well, as you have to remember to explicitly raise the PropertyChanged notification. (Although Fody is very powerful in this regard and can spot that a property getter on your ViewModel depends on other property getters.) Here's an example of a calculated FullName property in a C# ViewModel:
private string firstName;
public string FirstName
{
get { return firstName; }
set
{
if (firstName!= value)
{
firstName= value;
OnPropertyChanged("FirstName");
OnPropertyChanged("FullName");
}
}
}
public string FullName
{
get { return FirstName + " " + Surname; }
}
Knockout's solution to this is once again elegant and simple. You simply declare a ko.computed
type on your ViewModel:
this.fullName = ko.computed(function() {
return this.firstName() + " " + this.lastName();
}, this);
Elegant handling of binding to parent and root data contexts
Another area that causes regular pain for me with XAML databinding is when you need to bind to your parent's context or the root context. I think I've just about got the syntax memorized now, but I must have searched for it on StackOverflow hundreds of times before it finally stuck. You end up writing monstrosities like this:
...Binding="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type Window}}, Path=DataContext.AllowItemCommand}" ...
In knockout, once again, the solution is simple and elegant, allowing you to use $parent to access your parent data context (and grandparents with $parent[1]
etc), or $root
. Read more about knockout's binding context here.
<div data-bind="foreach: currentQuestion().answers">
<div data-bind="html: answer, click: $parent.currentQuestion().select"></div>
</div>
Custom binding extensions!
Finally, the killer feature. If only we could add custom binding expressions to XAML, then maybe we could work around some of its limitations and upgrade it with powerful capabilities. Whilst I have a feeling that this is in fact technically possible, I don't know of anyone who has actually done it. Once again knockout completely knocks XAML out of the water on this front, with a very straightforward extensibility model for you to create your own powerful extensions. If you run through the knockout tutorial, you'll implement one yourself and be amazed at how easy it is.
Conclusion
I've only used knockout.js for a few hours (here's what I made) and all I can say is I am very jealous of its powers. This is what data binding in XAML ought to have been like. XAML has been around for some time now, but there have been very few innovations in the data-binding space (we have seen the arrival of behaviours, visual state managers, merged dictionaries, format strings, but all of them suffer from the same clunky, hard to remember syntax). And now we have another subtly different flavour of XAML to learn for Windows Store 8 apps, it seems that XAML is here to stay. Maybe it is time for us to put some pressure onto Microsoft to give XAML data binding power to rival what the JavaScript community seem to be able to take for granted.
Comments
Agreed, knockout is great. Add Durandal to the mix and you have a great foundation for frontend apps.
AnonymousAnd for wpf/wp development you could also try out another project of Durandal author (clever guy) - Caliburn.Micro - it's make xaml more readable.
Anonymouscheers,
nilphilus
thanks nilphilus. I'll add Durandal to my (long) list of javascript frameworks to investigate. I haven't tried Caliburn yet - I've tended to use MVVMLight or roll my own for WPF apps.
Mark HWell said. XAML DataBinding is fabulous, but it was invented 8 years and hasn't evolved since. That simpler binding expression format is exactly what we've tried to do in MvvmCross - we've got really clear simple binding expressions (http://bit.ly/ZXOXFh) for Android and iOS ... and we're toying with the idea of porting these back to Xaml as well :)
SlodgeThe main benefits is that you can divided the overall look of your program away from the rule, By getting the rule by use of control bindings and activities it allows the business reasoning to be absolutely individual to the GUI.
Hamilton Sellers"But you can't call into a method on your ViewModel, or write expressions like this"
blorqGOOD! You should not be doing that in random places in the view!
@blorq - not sure I agree. Yes if your binding expressions are complex. But if you look at my very simple knockout example, you'll see how this saves creating trivial helper functions on the viewmodel. Not much different from using a "converter" with XAML in effect, and certainly much more readable.
Mark HExpression is a much better replacement to converters. I ended up writing converters such as TestAllTrue, TestAnyNotNull and SumNumbers etc for Silverlight/WPF. That's silly!
Anonymous