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
componentbinding 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
- can pass an
instanceproperty to use an object directly.
- can pass a
createViewModelproperty 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
requirekey to call the
requirefunction with the supplied value. This will work with whatever provides a global require function (like
require.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
elementproperty that supplies the id of an element to use as the template
- can be an
elementproperty that supplies an element directly
- can be a
requireproperty that like for
requiredirectly 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
params options. This allows for handling dynamic scenarios like rendering different components to the main content area depending on the state of the application.
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.
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:
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.
paramsobject supplied when using the custom element syntax will also include a
$rawproperty (unless the
paramshappens 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:
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.
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:
loadComponent. Both receive a callback argument that is called when the function is ready to proceed (to support asynchronous operations).
getConfigcan asynchronously return a configuration object to describe the component given a component name.
loadComponentwill take the configuration and resolve it to an array of DOM nodes to use as the template and a
createViewModelfunction 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:
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).
loadComponent function does the following:
- tries to resolve both the
templateportions of the config based on the various ways that it can be configured.
- if using
requirewith the configured module name and will take the result and go through the resolution process again.
- when it has resolved the
templateit will return an array of DOM nodes to use as the template and a
createViewModelfunction that will return a view model based on however the
viewModelproperty 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:
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.
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.