Learn

Architecture & pattern guides

Read the idea, build it on a Grid, prove it in a challenge. Short, practical guides — illustrated with RodGrid's own nodes — on system design, design patterns, flow modeling, and how to get the most out of the app.

All articles

MVVM: Model, View, ViewModel

In MVVM a ViewModel holds a screen's state in the form the screen shows it, and the View binds to it instead of being told what to show. Because the ViewModel never references the View, a test can drive it with no screen at all, though the binding brings its own surprises.

Three inputs, nine update paths

A bank's loan calculator has three inputs, the amount, the annual rate and the term in years, and three things that react to them: the monthly payment, the total interest, and an Apply button that works only when all three inputs make sense. For €200,000 at 4.5% over 25 years, the screen shows €1,111.66 a month. Any input can change any output, so a Presenter that pushes each value to the screen by hand, with each input's handler updating the outputs it affects, has nine dependencies to keep right, and a forgotten one leaves a stale figure on screen. MVP: Model, View, Presenter covers that design and its price.

MVVM keeps the testable object in the middle and drops the hand-written pushes. It turns the direction around: instead of being told what to show, the screen declares what it shows, and a framework keeps it current.

State the screen can bind to

A ViewModel holds everything the screen shows, already in the form the screen shows it: amountText, rateText, termText, monthlyPaymentText ("€1,111.66"), totalInterestText, and an apply command. The Model behind it is the loan domain: the repayment formula and the bank's limits on amount and term. The ViewModel calls the Model and formats its answers. It holds no text box and no label, and it has no reference to any screen.

apply is a command: an object the button invokes, which also answers whether it may run right now, usually through a method called canExecute. Where the framework supports command binding, as WPF does, a button bound to it follows that answer and stays disabled while the inputs are invalid; the command only has to announce when its answer may have changed, and nobody writes the code that greys the button out. That is the Command pattern applied to a button; Command covers the pattern itself.

What the binding layer does

The View declares what each widget is bound to, often in markup: the amount box's text to amountText, the payment label to monthlyPaymentText, the Apply button to apply. A data binding layer, part of the UI framework, keeps the two sides in step. It does two jobs. It watches the ViewModel: when a property changes, the ViewModel announces the change and the binding updates the widgets bound to it; in WPF that announcement is the PropertyChanged event of the INotifyPropertyChanged interface, and the mechanism underneath is the Observer pattern. And it writes input back: when the user edits a field, the binding sets the bound property, on each keystroke or when the field loses focus, depending on the binding (a WPF TextBox waits for focus loss by default).

Follow the 20 when the user shortens the term from 25 years. It leaves the term box as the string "20" in termText, put there by the binding. The ViewModel's setter parses it, asks the Model, and stores "€1,265.30" in monthlyPaymentText and the new total in totalInterestText, each announcing its change; the binding hears it and the label shows the new payment. No line of your code says "set the payment label". The ViewModel changed its own state, and the binding did the rest.

Bound, not called

Arrows show references, not data flow. The View and a unit test reach the ViewModel the same way, and nothing in the ViewModel points back at either.

The rule that makes it testable

The pattern was named at Microsoft, by John Gossman in 2005, for WPF's declaratively bound XAML views; Angular's [(ngModel)] and Vue's v-model put the same two-way binding on a form field. What makes MVVM testable is one rule: the ViewModel never references the View. A test builds the ViewModel over a Model with the €200,000 over 25 years from above, sets rateText to "4", and reads monthlyPaymentText, expecting "€1,055.67", then clears amountText and checks that apply can no longer execute. There is no screen and no fake View, and the test reads state instead of checking which calls were made.

When the binding fights the user

Binding removes code, and with it the place where you could read what happens. The rate box shows the first cost. Bind it straight to a number, converting on every keystroke and writing the number back, and the user typing 4.5 gets stuck halfway: they type "4.", and if the conversion accepts that as 4, the binding writes "4" back and the decimal point vanishes under the cursor. Keep the raw text as its own ViewModel property, rateText, and until it parses, leave the Model alone and let apply report that it cannot execute.

Debugging moves too. A wrong payment arrives through a binding, not through a call you can follow, so you set breakpoints in setters. A binding to a misspelled property often fails quietly: WPF writes a binding error to the debug output and the label simply stays empty.

Then the bank adds a second mode: the customer types the monthly payment they can afford, and the term is computed from it. Now the payment and the term set each other. The customer types €1,200; the ViewModel computes a term, rounds it to whole years, recomputes the payment from the rounded term, and the binding replaces the €1,200 the customer typed with a different figure. With a few such properties feeding each other, saying which change caused which gets hard. Binding fits well while each piece of state has one place that edits it, as in the original calculator; Unidirectional data flow: actions, store & view starts from the problem it hits when it does not.

The widget that sneaks back in

The rule tends to break over something small. After a failed check, the screen should put the cursor in the rate box, and the quickest way is to hand the ViewModel a reference to that box. Now the ViewModel's tests need a text box, real or faked, and the ViewModel fits only a screen that has one. The fix keeps the references pointing one way: the ViewModel exposes state the View reacts to, such as which field holds the error, or raises an event the View handles by moving the focus itself.

The calculator, as a Challenge

The Model-View-ViewModel Challenge is this article's diagram with the requirements written out. Two of them seem to pull against each other: changes flowing both ways through the binding, and no reference from the ViewModel to the View. Both hold if the View's edge carries a two-way label and no arrow comes back. The intent it asks you to make clear, logic you can test, has a natural picture: the unit test as a Client node, reaching the ViewModel by the same road as the View.

Practice this