TypeScript strongly typed Backbone Models

TypeScript and Backbone are a great fit in terms of writing simple, Object-Oriented code using JavaScript.  The simplicity of Backbone, coupled with the TypeScript generated closure syntax allow one to simply use the TypeScript extends keyword to derive from any of Backbone’s base classes – and start implementing functionality immediately.  This is a very natural way to write Object-Oriented JavaScript:

class ListItem extends Backbone.Model {

}

The problem with Backbone Models

Unfortunately, Backbone uses object attributes to store Model properties, and these need to be set in order for the Backbone model to work correctly.  The following code shows how to set Backbone Model properties:

class ListItem extends Backbone.Model {
    constructor() {
        super();
        this.set('Id', '2');
    }
}

The nature of the set and get functions of Backbone, however do not have any inherent type-safety.  These model properties are also not exposed as first-class object properties, and must always be accessed via the setter and getter functions.  A more natural way of expressing Backbone Models would be as follows:

class ListItem extends Backbone.Model {
    Id: number;
    Name: string;
    constructor() {
        super();
        this.Id = 2;
        this.Name = "ModelName";
    }
}

Using ES5 getter and setter syntax

The above effect can be achieve by using ES5 getter and setter syntax.  By defining a get and set function for each property, and then in turn calling the Backbone get and set functions, we can keep our Backbone Model in-synch with our TypeScript properties.

Note that you will need to switch your TypeScript project properties to compile to ES5 in order for the following code to work:

class ListItem extends Backbone.Model {
    get Id(): number {
        return this.get('Id');
    }
    set Id(value: number) {
        this.set('Id', value);
    }
    set Name(value: string) {
        this.set('Name', value);
    }
    get Name(): string {
        return this.get('Name');
    }

    constructor() {
        super();
        this.Id = 1;
        this.Name = "ModelName";
    }
}

Model Type-safety

We can further improve our model’s type-safety by using an interface – both for the implements keyword ( forcing our class to implement getters and setters for each interface property – and also for the object constructor.

Consider the following code:

interface IListItem {
    Id: number;
    Name: string;
    Description: string;
}

class ListItem extends Backbone.Model implements IListItem {
    get Id(): number        { return this.get('Id'); }
    set Id(value: number)   { this.set('Id', value); }
    set Name(value: string) { this.set('Name', value); }
    get Name(): string      { return this.get('Name'); }

    constructor(input: IListItem) {
        super();
        this.Id = input.Id;
        this.Name = input.Name;
    }
}

Note that we have defined an interface ( IListItem ), and forced the ListItem object to implement the interface.  We have also added the interface to the constructor, further enhancing our type-safety.

This ensures that changes to the interface will generate compile-time errors if the object does not have corresponding getter and setter functions, and also ensures that any object passed via the constructor will have all properties defined.

Simplifying the constructor

For a Backbone model with many properties, the constructor can further be simplified by looping through the properties of the input parameter as follows:

interface IListItem {
    Id: number;
    Name: string;
}

class ListItem extends Backbone.Model implements IListItem {
    get Id(): number { return this.get('Id'); }
    set Id(value: number) { this.set('Id', value); }
    set Name(value: string) { this.set('Name', value); }
    get Name(): string { return this.get('Name'); }

    constructor(input: IListItem) {
        super();
        for (var key in input) {
            if (key) {
                this[key] = input[key];
            }
        }
    }
}

A Jasmine Unit test

The above code will allow for both standard get and set Backbone syntax, as well as type-safe TypeScript syntax as shown in the following unit test:

describe("SampleApp : tests : models : ListItem_tests.ts ", () => {
    it("can construct a ListItem model", () => {
        var listItem = new ListItem(
            {
                Id: 1,
                Name: "TestName",
            });
        expect(listItem.get("Id")).toEqual(1);
        expect(listItem.get("Name")).toEqual("TestName");

        expect(listItem.Id).toEqual(1);

        listItem.Id = 5;
        expect(listItem.get("Id")).toEqual(5);

        listItem.set("Id", 20);
        expect(listItem.Id).toEqual(20);
    });

});

Note how a List Item is constructed with a standard JavaScript object definition.  Also, both listItem.Id or listItem.set(‘Id’, 20) syntax can be used to interact with a type-safe TypeScript Backbone model.

Have fun,

blorkfish.

Note : This blog post was as a result of a question posted on stackoverflow.com

Advertisements

3 Responses to TypeScript strongly typed Backbone Models

  1. Robin says:

    Thanks! This is a great article.

    Have you ever attempted to use Backbone Relational with Typescript?

  2. Robin says:

    For anyone targeting older browsers and therefore have to compile your TypeScript to ES3, the getter and setter syntax isn’t an option. But you can still write convenience methods on your model with the appropriate return types. e.g. getName() : string { return this.get(“name”)}

  3. Eirik Hoem says:

    Seems like you know your way around TS, so I’m shooting you a question that’s somewhat unrelated to this post.

    I’m working with Backbone, and Backbone Stickit, and I’m trying to figure out how I can tell TypeScript that the View class now has a new pubic method: myViewInstance.stickit(). It’s not quite clear how to do this via the definitions..

    Any help would be greatly appreciated!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: