Ember's Not Magic


Where do changes come from?

One of the simplest Ember-is-cool demos is the firstName lastName input demo. A full name is computed, and always stays up to date as you edit inputs. The js code for it looks like this:

App.IndexController = Ember.Object.extend({
    firstName: "Sam",
    lastName: "Gamgee",
    fullName: function() {
        return this.get('firstName') + " " + this.get('lastName');
    }.property('firstName', 'lastName')
});

Now I shall unscrupulously use my console to obtain a global reference to the userController, so I can do what I like with it:

> window.userController = App.__container__.lookup('controller:index');
> userController.set('firstName', 'Frodo');

If I make this call, how can Ember know to update the page? In a simple implementation (used by our tests), it doesn't. What Ember needs is notice as to when code that modifies application state has just finished running so that it can rerender the page as necessary. In order to know this your code must execute inside a function that Ember defines, and once the function exits the cleanup can be done. Picture it like this:

function emberWrapper(userFunc) {
    userFunc.call();
    doEmberCleanup();
}
document.onLoad = emberWrapper(function() {
    userController.set('firstName', 'Samwise');
}));

Now in doEmberCleanup all needed logic for DOM updating can be defined. At this point you may recognize emberWrapper: it is Ember.run, the taskmaster of the feared (and poorly named) Ember run loop. You can (and in test code, we do) call it directly passing any function you like. The main cleanup tasks that will be performed are updating bindings and rendering templates.

Obviously we don't call Ember run in application code. It is done for us -- so well that we rarely have to worry about it. An application's views' event handlers' start run loops, as do jQuery ajax responses. Running set from the console will start a run loop. Ember.run.later is a provision for setting timeouts that will trigger a later action inside Ember.run. While the specifics of how Ember manages this are complicated, the core concept is very simple. What the loop doesn't do is run in regular intervals checking for application state updates. If the run loop is the heart of Ember, it has tachycardia.

So we set firstName.

Ember.run(function() {
    userController.set('firstName', 'Frodo');
});

Because nothing more complicated is going on, firstName is stored as userController.firstName. Has fullName changed at this point? It is a computed property, and its value is not cached in the same place. You can see the cached value with Ember.meta(userController).cache.There's nothing there yet. Ember is lazy about computing values it doesn't need. Now if you get fullName, you will see its value show up in the cache. You could set 100 different firstNames and Ember would not recompute fullName until somebody asked to see it.

Obviously in our example, we are asking to see fullName. But in a regular app, who is? The answer is bindings. Ember maintains a big ole one dimensional list of all the bindings you've created anywhere in your entire application, and as part of Ember.run's cleanup it loops through them. It will get from updated side of the binding, and set on the other. In this case, we have in a template somewhere. The binding is not to the template itself, but rather to an internal view object which happens to be responsible for that tiny part of the template. In this case that view will have only value property, bound to fullName. If you want to watch this happening, set Ember.LOG_BINDINGS to true! (Note: Ember 1.9 optimized these bindings away) Here is what that looked like when I did it a moment ago:

Ember.Binding<ember257>(_parentView.context.firstName -> value) <- Frodo

The next step in the run loop, after binding sync, is rendering. The things that are rendered are views, and, excepting custom logic, controllers and models are out of the picture as the relevant values have already been bound. Our view was bound a new value, and must be rerendered. A functional way to do this would be for the view to have an element whose innerHTML would be replaced with the new value, however as this is a very common use case there has been further optimization. The actual view bound to is a SimpleMetamorphView which, to avoid DOM clutter, doesn't have an element of its own.

What does this mean? It means that when we define a computed property, we don't really know when its definition will be executed. If it is gotten in app code, it will be executed at that time. If not though, it will be executed somewhere inside the big loop updating bindings. If it is dead code, perhaps it won't run at all. All of this is to say that, in a single run loop it could run more than once, only once, or zero times all at very different points in the lifecycle.

My point is this: DO NOT SET PROPERTIES INSIDE THE DEFINITION OF A COMPUTED PROPERTY. Do not call functions with side effects, do not set other computed properties. Do not pass GO do not collect $200. We frequently have things like this in our applications:

Ember.Controller.extend({
    fullName: function() {
        var fullName = this.get('firstName') + " " + this.get('lastName');
        this.travelerProfile.creditCard.set('fullName', fullName);
        return fullName;
    }.property('firstName', 'lastName');
});

If on the page where firstName and lastName are being changed fullName is not also displayed, the external reference will never even be updated, or not until a very diffrent point. Somebody could even cause bugs in business logic with a template change. This is exactly what handlebars is designed to prevent, and why we are using it instead of Coldfusion.

A better way to write it? Create a binding! (Or create a settable computed property by accepting parameters in your property definition function, but I'll discuss that at a later date.)

Ember.Controller.extend({
    fullNameBinding: 'travelerProfile.creditCard.fullName'
});

So there it is, end to end, the lifecycle of a property change as it progagates through an Ember app: event handler creates a run loop which sets a property. The run loop ends, bindings sync, templates are rendered, the DOM updates, the event handler ends, the page updates visually.

Ember.run(function() {
    userController.set('lastName', 'Don\'t wear the ring');
});