Durandal and Object.defineProperty

Thursday, March 20, 2014

DurandalJS continues to make dramatic improvements under the direction of lead architect Rob Eisenberg. The framework is easy to pick up since the API is small, well designed, fully featured, and uses technologies familiar to many JavaScript developers, like jQuery, Knockout, and RequireJS.

I’ve been working with Durandal 2.0.1 and my favorite feature, by far, is the observable plugin which allows binding to plain JavaScript objects.

When creating the application, I only need to tell Durandal to use the observable plugin.

define(function(require) {
    var app = require("durandal/app");
    app.configurePlugins({        
        observable: true // <-
    });

    app.start().then(function() {
        app.setRoot("{viewModelName}", "entrance");
    });
});

And now all my view models can be plain, simple objects.

define(function(require) {
    var dataService = require("data/movieData");
    var viewModel = {

        movies: [],

        activate: function() {
            dataService.getAll().then(function(newMovies) {
                viewModel.movies = newMovies;
            });
        }
    };
    return viewModel;
});

How’s It Work?

At the core of the observable plugin is Object.defineProperty. This ES5 API requires a compatible browser, but fortunately even IE9 has support. The defineProperty method can build a property with get and set logic, as in the following example.

var person = {};

var propertyDefinition = function(value) {
    
    var get = function(){
        return value;
    };

    var set = function(newValue) {
        if(value != newValue) {
            value = newValue;
            console.log("Value changed to " + value);
        }
    };

    return {
        configurable: true,
        enumerable: true,
        get: get,
        set: set
    };    
};

Object.defineProperty(
    person, 
    "firstName", 
    propertyDefinition()
);

person.firstName = "Scott";
person.firstName = "Allen";
console.log(person.firstName);

With Durandal, you never have to worry about using defineProperty directly. Durandal’s observable plugin provides an API to convert all the properties of an object into Knockout compatible observables using defineProperty.

define(function(require){
    var observable = require("durandal/plugins/observable");

    var person = {
        firstName: "Scott",
        lastName: "Allen"
    };

    observable.convertObject(person);
    
    // can still use properties as properties instead of as functions
    person.firstName = "Ethan";
    console.log(person.firstName);
});

If we look at the object in the debugger, we’ll see the following.

Observable with defineProperty

But even the above code is something you don’t need to write, because Durandal will automatically convert view models into observable objects ready for 2 way data binding before bindings are applied. There’s a lot to be said for frameworks that care about making things easy for the developer.


Comments
gravatar Robert Slaney Thursday, March 20, 2014
Just a small note regarding the use of Object.defineProperty. Chrome has a memory/resource leak with this due to optimizations in their V8 engine. (https://code.google.com/p/v8/issues/detail?id=2073)
gravatar MattB Friday, March 21, 2014
Hi, where do you tell durandal to use the observable plugin? I thought it would be in the config but the code above doesn't seem to show that as far as I can tell?
gravatar Scott Friday, March 21, 2014
@Robert: thanks for pointing that out. @MattB: I actually forgot that in the code (updated now), it's during app.configurePlugins.
gravatar 4imble Friday, March 21, 2014
So if you enable this, all properties are observable? Wont that make it much harder if you want to perform some optimisations like throttling? Can you prevent some properties from being observable too if you know they are never going to change?
gravatar Scott Saturday, March 22, 2014
@4imble: will have to check on that one, not sure.
gravatar Cory House Monday, March 24, 2014
It's great that ES5 support is now so ubiquitous. I assume this is why Steve Sanderson has stepped away from supporting the Knockout Mapping plugin. I continue to use his mapping plugin so I can assure old IE support when working in Durandal. I'm aware of three ways to save yourself the work of declaring observables manually: 1. Knockout mapping plugin https://github.com/SteveSanderson/knockout.mapping 2. Knockout ES5 plugin https://github.com/SteveSanderson/knockout-es5 3. Durandal observable plugin discussed above (if working with Durandal, obviously) Great post Scott!
Comments are now closed.
by K. Scott Allen K.Scott Allen
My Pluralsight Courses
The Podcast!