Knockout 3.2 will include some exciting new functionality out-of-the-box to do modular development through creating components. From Knockout’s point of view, a component allows you to asynchronously combine a template and data (a view model) for rendering on the page. Components in Knockout are heavily inspired by web components, but are designed to work with Knockout and all of the browsers that it supports (all the way back to IE6).
Components allow you to combine independent modules together to create an application. For example, a view could look like:
1 2 3 4 5 |
|
The idea of doing modular development with Knockout is certainly not a new one. Libraries like Durandal with its compose
binding and the module
binding from my knockout-amd-helpers have been doing this same type of thing for a while and have helped prove that it is a successful way to build and organize Knockout functionality. Both of these libraries have focused on AMD (Asynchronous Module Definition) to provide the loading and organization of modules.
Knockout’s goal is to make this type of development possible as part of the core without being tied to any third-party library or framework. Developers will be able to componentize their code, by default, rather than only after pulling in various plugins. However, the functionality is flexible enough to support different or more advanced ideas/opinions through extensibility points. When KO 3.2 is released, developers should seriously consider factoring components heavily into their application architecture (unless already successfully using one of the other plugins mentioned).
How does it work?
By default, in version 3.2, Knockout will include:
- a system for registering/defining components
- custom elements as an easy and clean way to render/consume a component
- a
component
binding as an alternative to custom elements that supports dynamically binding against components - extensibility points for modifying or augmenting this functionality to suit individual needs/opinions
Let’s take a look at how this functionality is used:
Registering a component
The default component loader for Knockout looks for components that were registered via a ko.components.register
API. This registration expects a component name along with configuration that describes how to determine the viewModel
and the template
. Here is a simple example of registering a component:
1 2 3 4 5 6 |
|
The viewModel key
- can be a function. If so, then it is used as a constructor (called with
new
). - can pass an
instance
property to use an object directly. - can pass a
createViewModel
property to call a function that can act as a factory and return an object to use as the view model (has access to the DOM element as well for special cases). - can pass a
require
key to call therequire
function with the supplied value. This will work with whatever provides a global require function (likerequire.js
). The result will again go through this resolution process.
Additionally, if the resulting object supplies a dispose
function, then KO will call it whenever tearing down the component. Disposal could happen if that part of the DOM is being removed/re-rendered (by a parent template or control-flow binding) or if the component binding has its name changed dynamically.
The template key
- can be a string of markup
- can be an array of DOM nodes
- can be an
element
property that supplies the id of an element to use as the template - can be an
element
property that supplies an element directly - can be a
require
property that like forviewModel
will callrequire
directly with the supplied value.
A component could choose to only specify a template
, in cases where a view model is not necessary. The supplied params will be used as the data context in that case.
The component binding
With this functionality, Knockout will provide a component
binding as an option for rendering a component on the page (with the other option being a custom element). The component binding syntax is fairly simple.
1 2 3 4 5 |
|
The component binding supports binding against an observable and/or observables for the name
and params
options. This allows for handling dynamic scenarios like rendering different components to the main content area depending on the state of the application.
Custom Elements
While the component binding is an easy way to display a component and will be necessary when dynamically binding to components (dynamically changing the component name), custom elements will likely be the “normal” way for consuming a component.
1
|
|
Matching a custom element to a component
Knockout automatically does all of the necessary setup to make custom elements work (even in older browsers), when ko.registerComponent
is called. By default, the element name will exactly match the component name. For more flexibility though, Knockout provides an extensibility point (ko.components.getComponentNameForNode
) that is given a node and expected to return the name of the component to use for it.
How params are passed to the component
The params are provided to initialize the component, like in the component binding, but with a couple of differences:
- If a parameter creates dependencies itself (accesses the value of an observable or computed), then the component will receive a computed that returns the value. This helps to ensure that the entire component does not need to be rebuilt on parameter changes. The component itself can control how it accesses and handles any dependencies. For example, in this case:
1
|
|
The component will receive a params object that contains a name
property that is supplied as a computed in this case. The component can then determine how to best react to the name
changing rather than simply receiving the result of the expression and forcing the entire component to re-load on changes to either of the observables.
- The
params
object supplied when using the custom element syntax will also include a$raw
property (unless theparams
happens to supply a property with that same name) which gives access to computeds that return the original value (rather than the unwrapped value). For example:
1
|
|
In this case, since selectedItem
is accessed, the param is supplied as a computed. When the computed is accessed, the unwrapped value
is returned to avoid having to double-unwrap a param to get its value. However, you may want access to the value
observable in this case, rather than its unwrapped value. In the component, this could be achieved by accessing params.$raw.value()
. The default functionality is slanted towards ease of use (not having to unwrap a param twice) while providing $raw
for advanced cases.
Custom loaders
Knockout let’s you add multiple “component loaders” that can choose how to understand what a component is and how to load/generate the DOM elements and data.
A loader provides two functions: getConfig
and loadComponent
. Both receive a callback argument that is called when the function is ready to proceed (to support asynchronous operations).
getConfig
can asynchronously return a configuration object to describe the component given a component name.loadComponent
will take the configuration and resolve it to an array of DOM nodes to use as the template and acreateViewModel
function that will directly return the view model instance.
The default loader
To understand creating a custom component loader, it is useful to first understand the functionality provided by the default loader:
The default getConfig
function does the following:
- this function simply looks up the component name from the registered components and calls the callback with the defined config (or null, if it is not defined).
The default loadComponent
function does the following:
- tries to resolve both the
viewModel
andtemplate
portions of the config based on the various ways that it can be configured. - if using
require
will callrequire
with the configured module name and will take the result and go through the resolution process again. - when it has resolved the
viewModel
andtemplate
it will return an array of DOM nodes to use as the template and acreateViewModel
function that will return a view model based on however theviewModel
property was configured.
A sample custom loader
Let’s say that we want to create a widget
directory where we place templates and view model definitions that we want to require via AMD. Ideally, we want to just be able to do:
1
|
|
In this case, we could create a pretty simple loader to handle this functionality:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
|
In this custom loader, we just dynamically build the configuration that we want, so we don’t necessarily have to register every “widget” as its own component, although registering will properly setup custom elements to work with the component. Loading the widget-one
component would load a one.js
view model and one.tmpl.html
template from a widgets
directory in this sample loader. If the component is not a “widget”, then the callback is called with null
, so other loaders can try to fulfill the request.
Summary
Components are a major addition to Knockout’s functionality. Many developers have found ways to do this type of development in their applications using plugins, but it will be great to have standard support in the core and the possibility for extensibility on top of it. Steve Sanderson recently did a great presentation at NDC Oslo 2014 that highlighted the use of components in Knockout. Check it out here.
Knockout 3.2 is well underway and should be ready for release this summer.