Knock Me Out

Thoughts, ideas, and discussion about Knockout.js

Working With AMD Modules in Knockout.js

| Comments

After being a bit resistant at first, I have happily moved to using require.js to manage dependencies in most of the Knockout.js applications that I have written in the last year or so. With each application, I have tried a number of patterns to make it easier to work with AMD (Asynchronous Module Definition) modules. Recently, I decided to formalize some of these patterns into a lightweight plugin that makes it simple to bind against modules and pull in templates that live in external files.

The result is this library: knockout-amd-helpers

Why require.js and AMD modules

Breaking an application down into small, decoupled modules is a pattern that works well when done right. Creating modules in JavaScript is often accomplished by using various namespacing patterns. As an application grows though, it is easy to lose sight of the individual dependencies of each module and eventually maintaining the order that all of your scripts need to be loaded in becomes a burden as well. AMD module loaders can help with these issues.

There are plenty of good resources that help explain require.js, dependency management, and AMD modules. Here are a few good ones:

  • The require.js documentation on AMD modules.
  • Our documentation on using Knockout with require.js.
  • My co-worker Jonathan Creamer’s tutorial on using Knockout.js in large-scale applications. He also recently did a presentation for dotnetConf that can be found here on using require.js and using it with ASP.NET MVC.
  • While require.js is the most mentioned option in this category, there are others including the excellent curl.js.

A few pain points

When developing Knockout applications using require.js, there are a couple of situations that out-of-the-box still seemed less than perfect to me.

1- After breaking down your application into specific modules, it is unfortunate to have to include all of your markup on a single page. There are server-side solutions to pull in partials for your templates, but that still results in a page that contains all of your markup from the start. There is also the external template engine, which works well, but is not designed to leverage the capabilities of the AMD loader libraries (like optimization/bundling).

2- Dynamically requesting and binding against modules is not trivial. Normally, you would build a main “App” view model that contains all of the sub-modules that you want to bind against. It would be nice though to be able to dynamically pull in modules or use modules as reusable components in an app under any context.

Looking at potential solutions

Based on some of the different approaches that I have used in the past, I created the knockout-amd-helpers plugin to hopefully help ease these issues.

An AMD compatible template engine

The first feature of the plugin is to use one of Knockout’s extensibility points to replace the default template engine with one that is able to load named templates using the AMD loader’s text plugin.

I used the template sources extensibility point that I discussed here. When a named template is requested, it first checks to see if there is a script tag with the specified id (the default engine actually will grab any element with that id) and if not uses the text plugin to require the template. This will asynchronously load the template (unless it has already been loaded or is bundled and already available on the client). The template source uses an observable that triggers the template binding to update when the template is available.

For example, when using a binding like:

1
    <ul data-bind="template: { name: 'itemView', foreach: items }"></ul>

If there is no script tag with an id of itemView, it will attempt to load the template. The engine uses a default path of templates and a default suffix of .tmpl.html. So, it would look for the template at templates/itemView.tmpl.html. The default path and suffix can be configured and you can also pass a more specific path in for the name (sub/path/itemView).

A module binding

This updated template engine now helps keep your templates as modular as your view models, but it still would be nice to easily pull modules into an application without using a top-level view model that needs to contain everything that you might want to bind against.

To help with this situation, the plugin includes a module binding, which offers a flexible way to dynamically pull a module into your markup. Here is a basic example:

1
    <nav data-bind="module: 'navigation'"></div>

This will require a navigation module (the base directory can be configured as well). If the main element had children, then it would use them as an inline/anonymous template. However, in this case, since the element does not have children, it will use navigation as the template name and follow the template engine’s rules for pulling in the template.

After the loading the module it will follow a few rules for deciding what data to bind against:

  1. If the module returns a function, then it will create a new instance
  2. If the module returns an object, then it will, by default, call an initialize function on the object (if it exists) and either use the result of the function or the original object if there is not a return value.

This gives you the flexibility of either constructing a new object, using an existing object, or calling a function that returns some object to bind against.

The module binding has a number of options that you can pass in as well. Here is an example passing all of the options:

1
2
    <div data-bind="module: { name: 'one', data: initialData, template: 'oneTmpl',
                              initializer: 'createItem', afterRender: myCallback }"></div>

These options let you do things like customize the template to use, specify data to pass into the constructor or initializer, define the name of the function to call, and pass an afterRender function through to the template binding.

With this binding, it is possible to call ko.applyBindings({}) and build your application strictly using the module binding. A module binding can be nested inside other module bindings and you can also use observables to dynamically specify your module.

Communicating between modules

When building an app using this structure, it would be less than ideal to rely on $root or $parent calls within the markup to communicate between modules. This can potentially couple your module to only working within a certain context. While this can certanily work, a better solution may be to use some type of messaging to communicate between the modules.

I have a library called knockout-postbox that adds a ko.postbox object and some observable extensions that make it easy to publish values on a topic and update an observable based on a subscription to a topic.

For example, in a module that defines the main content, you could have an observable like:

1
   this.sectionName = ko.observable().subscribeTo("navigation.current");

Then, in the navigation module, publish on that topic like:

1
   this.selectedNavItem = ko.observable().publishOn("navigation.current");

The postbox library supports a number of options that control how the publish/subscribe process works, but if you are looking for a stand-alone library that supports a number of additional features (channels, wildcards, envelopes), then I would recommend postal.js

Summary

If you are using AMD modules in your Knockout application and are looking for a lightweight and simple, but flexible way to bind against templates and modules, then check out this new library. It has been tested with both require.js and curl.js. I would be happy to help support any other AMD loaders, if there is interest. Please check out the README on the repository for additional documentation. I also plan to work on a better example, as time permits.

Comments