Related tags

Bringing Angular 2+ Development Experience into Backbone

17 August 2017

I've been working with Backbone for years and it suited me pretty well. I do love its conciseness. It gives the desired abstraction and yet leaves you at the full control of your app. When you need to know what exactly is happening in your app behind your code, it's matter of minutes to go though the Backbone annotated sources and figure out the flow. That's something you hardly can afford with other frameworks such as Angular, React, Vue or Amber. Besides it's ridiculously small - it's less than 8Kb without dependencies where the dependencies can be dropped if you go with Exoskeleton and Backbone.NativeView. I know that few today care about download size, but I do as it still affects the user response time.

What I do not like about Backbone that it provides no data binding out of the box. We have to do it manually, repeating ourselves with every single view, resulting in verbose and clumsy app code. Also Backbone has no embedded template engine. View simply binds to existing node or to immediately created one. By default it doesn't render a component, but we can populate the bounding node manually. Canonically we use text-based template engines like mustache.js, Handlebars or _.template. So every view rendering means obtaining of new HTML text and replacing view content with it. Actually we kill the current DOM-subtree and replace it with a new one. This is a problem as we lose this way internal node references and DOM event handlers. Fortunately Backbone can delegate DOM events to the bounding node, which doesn't get destroyed during renderings. In particular the bindings specified in `this.events` property still work after rendering. So we are good until it comes to web forms. After rendering we lose the form state. Just imagine a typical task. You have a form where user input is validated on typing. The form shows an error message under the field with every typed character unless the filed value is valid. Here you either go with a separate micro-template per a message container or do it without any template at all. I think that despite the unbeatable performance of text-based template engines they are a wrong choice for the Web. Unsubscribing listeners, removing DOM-subtree, injection a new one, re-subscribing on every state change seems too me as overkill. Just compare it to a DOM-based template, which doesn't kill anything, but updates properties of the target node when the associated data changes.

Backbone.declarative

As you can see the suggested solutions are more about a declarative way (like Backbone events hash) to bind model/collection. But what if view has multiple data sources? E.g. it's a form where there is an input, which datalist is populated from one collection and a select, which options pulled in by another collection. But what if the requirements to validate the form on user input? Remember, we deal still with a text based template.

Superset Epoxy.js provides more sophisticated approach. They allow to bind multiple sources and take advantages of inline binding:

It looks like a working solution, but what would rather go still with a DOM-based template like Angular. And here a Backbone-extension ngBackbone that I would like to talk about. It extends Backbone.View with an abstract View, which enables ngTemplate-powered binding. It brings also a further abstraction level FormView that unlocks HTML5 Form API in the template. And it's written in TypeScript...

As for me TypeScript is the next big thing about JavaScript. It feels like shift to version 5 in PHP world a decade ago that brought abstract classes, interfaces and members visibility. Besides Type Script is a pretty good ES.Next transpiler. So if you're not familiar with the language I would would encourage you to get started with it. Especially while Backbone turns out to be quite friendly with TypeScript.

Components

Well, how does ngBackbone work? Like in Angular or in ReactJs we operate with components. So we can start with a simple HTML:

As you see, very like in Angular 2 we use a decorator @Component for the view declarations. We bind the view to the elements ng-hello and make it generating the content from the provided template string. When running this page we get `Hello World!` content rendered within out bounding element.

As it expected from components we can easily create a tree of them by attaching components one to another:

You can find more about communication between components, lazy component initialization, injectable options and shared state in the documentation.

Data-binding

ngBackbone advocates a very simple idea of multi-source bounding. The we can use models and collections options that contain maps of named data sources. For example we can bind a model under scope name hero as follows:

This makes the model available within the template. So we can access its properties with ngTemplate directives as hero.propertyName. ngTemplate supports a number of directives where `ng-text` simply replaces the node text with actual state of the bound data.

We declare a named collection worlds and address it from `ng-for` template directive. Similar to models we can access the collection within view body as this.collections.get( "worlds" ).

Web-Forms

With the plain Backbone we usually manually bind form inputs to a model and run model's validate method by saving or setting the model. It's a minimalistic and very limited approach. That's why there are so many extensions like

ngBackbone doesn't implement own validation functionality, instead it relies on HTML5 Form API. It

exposes a module FormView that automatically creates internal model for any view group marked with `ng-group` directive and synchronizes it the element ValidityStates. Also one state model gets created for the group the represents accumulative states like valid, dirty and validationMessage.

We have here a component, which builds a form named `hello`. It contains an input field set as required by following HTML5 notation. Under the field we have a container that shows up only after we've touched the input (started typing) and it displays a validation error for this filed if any available. Note how we access the field state. We start with declared group name `hello`, then goes input name `name` and it ends with state property `validationMessage` (hello.name.validationMessage). You can find all the available state properties in the documentation, but in fact those are properties of HTML5 ValidityState.

So we just need to declare a validator with a callback that returns a Promise, resolved when the conditions are met and rejected with validation message content otherwise. This way we can also target multiple validators in a form field:

<input data-ng-validate="foo, bar, baz" />

This promisable validation API makes it easy to validate remotely with asynchronous XHR calls. For example, if we want to validate on-typing a form field against the list of existing on the server emails we can do it like that:

Here we pass custom validator to the component as a class extending built-in one FormValidators. This way we can apply `@Debounce` validator, stating that the validator `name` is called no more then once per 350ms regarding of user typing speed.

Recap

ngBackbone enables Angular-like programming experience by using DOM-based templates and component-driven approach. It separates declarative and imperative view codes by using @Component decorator. It synchronizes HTML5 Form API validity state to component controls and group internal state models available in the template. ngBackbone allows to specify custom asynchronous validators and apply them to form controls declarative way. Eventually it encourages fluent TypeScript programming experience in Backbone.

ngBackbone is a small extension of Backbone.js that unlocks Angular-like programming experience

I've been working with Backbone for years and it suited me pretty well. I do love its conciseness. It gives the desired abstraction and yet leaves you at the full control of your app. When you need to know what exactly is happening in your app behind your code, it's matter of minutes to go though the Backbone annotated sources and figure out the flow. That's something you hardly can afford with other frameworks such as Angular, React, Vue or Amber. Besides it's ridiculously small - it's less than 8Kb without dependencies where the dependencies can be dropped if you go with Exoskeleton and Backbone.NativeView. I know that few today care about download size, but I do as it still affects the user response time.

Who's the dude?

Dmitry Sheiko is a web-developer living and working in Frankfurt am Main, DE