What’s all this fuss about data-binding?

Graham Kaemmer
PatientBank Engineering
4 min readJan 25, 2017

How do we keep our UI in sync with the data it uses? This is the fundamental problem of the frontend web. It’s the reason the javascript landscape is littered with “view layers”, MVC and MVVM frameworks, and fancy state containers.

For example, one very popular javascript library forces you to call a special function setState when you want to update data that may affect UI; another very popular library meticulously wraps all of your application code in special $digest loops so that afterwards it can explicitly re-render any UI that may have changed.

But what’s the point of all this? We face the simple challenge of turning javascript data into DOM — why must we come up with special ways of storing, updating, or accessing our data?

To understand the problem, let’s look at a function that renders a little VanillaJS component:

function render(person) {
var el = document.findElementById('hello');
el.innerHTML = "Hello, " + person.name;
}

It’s obvious which data this function depends on: person.name. Yes, it takes a whole person as an argument, but the only value that affects its behavior is the name of the person. The person’s age, the person’s height, and their twitter handle do not affect the render at all.

Now, imagine we want to always keep the component up-to-date. Easy! Every time we update person.name we call render(person) afterwards. But this gets tedious — every new developer needs to know to do this, and maybe some of our old code updated person.name without rendering.

Maybe we can just call render(person) every few milliseconds? Meh, could work, but obviously wouldn’t be ideal for more expensive renders. And there would be a delay between data updates and UI changes.

One very clean approach is to override person's name getter and setter (assuming person is an instance of an ES6 class):

class Person {
_name;
get name() {
return this._name;
}
set name(newName) {
this._name = newName;
render(this);
}
}

Cool. Now all of our old code and future code will work (as long as we don’t do anything too crazy with our person.name property, like changing its descriptor).

But what if next week we update our render function to this:

function render(person) {
var el = document.findElementById('hello');
el.innerHTML = "Hello, " + person.name +
". It looks like you're already " + person.age + " years old. Yikes. 😬";
}

Annoying! Now render(person) depends on person.name and person.age. Now we have to do the same thing with the age property of Person:

class Person {
_name;
_age;
get name() {
return this._name;
}
set name(newName) {
this._name = newName;
render(this);
}
get age() {
return this._age;
}
set age(newAge) {
this._age = newAge;
render(this);
}
}

Great. But now if we have a renderFooter function that also depends on person.name, we’ll have to keep that in sync too:

function renderFooter(person) {
var el = document.findElementById('footer');
el.innerHTML = "This is the account of " + person.name;
}

Now we have two things to update in our name setter:

  set name(newName) {
this._name = newName;
render(this);
renderFooter(this);
}

This sort of callback whack-a-mole quickly becomes unsustainable. Not only is it hard to explicitly add a data dependency, but removing them when necessary becomes tedious as well.

Keeping UI in sync with data

What we need here is one of those “data-binding” systems, which will do two things:

  1. Listen for data changes.
  2. When the data changes, re-run code that depends on that data (e.g. rendering our components).

At PatientBank, we use MobX to do these things. It is by no means the only solution: we’ve tried AngularJS, React setState, and Redux; and countless other libraries exist for this very purpose.

In our minds, MobX outshines other options because it avoids dictating how to store and update your state. We can use our own domain model classes like Person just like we did above. It works well with our views (React), but you can easily use it to run any code when data changes.

It also requires almost no boilerplate code. You can write the entire app above using MobX in just a few lines:

class Person {
@observable name; // These are a super fancy ES7 decorators
@observable age; // Optional, but awesome!
}
const person = new Person();
person.name = "Graham";
person.age = 22;
function render(person) {
var el = document.findElementById('hello');
el.innerHTML = "Hello, " + person.name +
". It looks like you're already " + person.age + " years old. Yikes. 😬";
}
function renderFooter(person) {
var el = document.findElementById('footer');
el.innerHTML = "This is the account of " + person.name;
}
autorun(() => render(person));
autorun(() => renderFooter(person));

The only things we’ve added are @observable decorators to the name and age fields of Person. @observable overrides getters and setters, tracking each time those properties are updated or accessed. Then, calling render and renderFooter in a MobX autorun is enough to subscribe to those trackable properties and re-render the components whenever they change.

Done! No need to play whack-a-mole — MobX whacks every mole automatically.

We can easily add more @observable fields to Person (or to any other class) and add more render functions that we want to run automatically. We can update our render functions and they will always subscribe to the right data. We can even refactor our views to use a library like React, all without changing our underlying data structures.

MobX has allowed us at PatientBank to think very little about how we keep our UI in sync with our data. Instead, we can focus on other things, like gathering thousands of medical records for our patients 😎.

You can learn more about MobX in its docs, or by watching an EggHead tutorial by its creator.

If you love simplifying complex systems, let us know. We’re looking for driven, creative and resourceful people to join our team. Take a look at our current openings, or drop us a line at careers@patientbank.us.

--

--