For Knockout to be able to load and instantiate your components, you must register them using ko.components.register, providing a configuration as described here.
Note: As an alternative, it’s possible to implement a custom component loader that fetches components by your own conventions instead of explicit configuration.
You can register a component as follows:
ko.components.register('some-component-name', { viewModel: <see below>, template: <see below> });
If no viewmodel is given, the component is treated as a simple block of HTML that will be bound to any parameters passed to the component.
Viewmodels can be specified in any of the following forms:
function SomeComponentViewModel(params) { // 'params' is an object whose key/value pairs are the parameters // passed from the component binding or custom element. this.someProperty = params.something; } SomeComponentViewModel.prototype.doSomething = function() { ... }; ko.components.register('my-component', { viewModel: SomeComponentViewModel, template: ... });
Knockout will invoke your constructor once for each instance of the component, producing a separate viewmodel object for each. Properties on the resulting object or its prototype chain (e.g., someProperty and doSomething in the example above) are available for binding in the component’s view.
If you want all instances of your component to share the same viewmodel object instance (which is not usually desirable):
var sharedViewModelInstance = { ... }; ko.components.register('my-component', { viewModel: { instance: sharedViewModelInstance }, template: ... });
Note that it’s necessary to specify viewModel: { instance: object }, and not just viewModel: object. This differentiates from the other cases below.
If you want to run any setup logic on the associated element before it is bound to the viewmodel, or use arbitrary logic to decide which viewmodel class to instantiate:
ko.components.register('my-component', { viewModel: { createViewModel: function(params, componentInfo) { // - 'params' is an object whose key/value pairs are the parameters // passed from the component binding or custom element // - 'componentInfo.element' is the element the component is being // injected into. When createViewModel is called, the template has // already been injected into this element, but isn't yet bound. // - 'componentInfo.templateNodes' is an array containing any DOM // nodes that have been supplied to the component. See below. // Return the desired view model instance, e.g.: return new MyViewModel(params); } }, template: ... });
Note that, typically, it’s best to perform direct DOM manipulation only through custom bindings rather than acting on componentInfo.element from inside createViewModel. This leads to more modular, reusable code.
The componentInfo.templateNodes array is useful if you want to build a component that accepts arbitrary markup to influence its output (for example, a grid, list, dialog, or tab set that injects supplied markup into itself). For a complete example, see passing markup into components.
If you have an AMD loader (such as require.js) already in your page, then you can use it to fetch a viewmodel. For more details about how this works, see how Knockout loads components via AMD below. Example:
ko.components.register('my-component', { viewModel: { require: 'some/module/name' }, template: ... });
The returned AMD module object can be in any of the forms allowed for viewmodels. So, it can be a constructor function, e.g.:
// AMD module whose value is a component viewmodel constructor define(['knockout'], function(ko) { function MyViewModel() { // ... } return MyViewModel; });
… or a shared object instance, e.g.:
// AMD module whose value is a shared component viewmodel instance define(['knockout'], function(ko) { function MyViewModel() { // ... } return { instance: new MyViewModel() }; });
… or a createViewModel function, e.g.:
// AMD module whose value is a 'createViewModel' function define(['knockout'], function(ko) { function myViewModelFactory(params, componentInfo) { // return something } return { createViewModel: myViewModelFactory }; });
… or even, though it’s unlikely you’d want to do this, a reference to a different AMD module, e.g.:
// AMD module whose value is a reference to a different AMD module, // which in turn can be in any of these formats define(['knockout'], function(ko) { return { module: 'some/other/module' }; });
Templates can be specified in any of the following forms. The most commonly useful are existing element IDs and AMD modules.
For example, the following element:
<template id='my-component-template'> <h1 data-bind='text: title'></h1> <button data-bind='click: doSomething'>Click me right now</button> </template>
… can be used as the template for a component by specifying its ID:
ko.components.register('my-component', { template: { element: 'my-component-template' }, viewModel: ... });
Note that only the nodes inside the specified element will be cloned into each instance of the component. The container element (in this example, the <template> element), will not be treated as part of the component template.
You’re not limited to using <template> elements, but these are convenient (on browsers that support them) since they don’t get rendered on their own. Any other element type works too.
If you have a reference to a DOM element in your code, you can use it as a container for template markup:
var elemInstance = document.getElementById('my-component-template'); ko.components.register('my-component', { template: { element: elemInstance }, viewModel: ... });
Again, only the nodes inside the specified element will be cloned for use as the component’s template.
ko.components.register('my-component', { template: '<h1 data-bind="text: title"></h1>\ <button data-bind="click: doSomething">Clickety</button>', viewModel: ... });
This is mainly useful when you’re fetching the markup from somewhere programmatically (e.g., AMD - see below), or as a build system output that packages components for distribution, since it’s not very convenient to manually edit HTML as a JavaScript string literal.
If you’re building configurations programmatically and you have an array of DOM nodes, you can use them as a component template:
var myNodes = [ document.getElementById('first-node'), document.getElementById('second-node'), document.getElementById('third-node') ]; ko.components.register('my-component', { template: myNodes, viewModel: ... });
In this case, all the specified nodes (and their descendants) will be cloned and concatenated into each copy of the component that gets instantiated.
If you’re building configurations programmatically and you have a DocumentFragment object, you can use it as a component template:
ko.components.register('my-component', { template: someDocumentFragmentInstance, viewModel: ... });
Since document fragments can have multiple top-level nodes, the entire document fragment (not just descendants of top-level nodes) is treated as the component template.
If you have an AMD loader (such as require.js) already in your page, then you can use it to fetch a template. For more details about how this works, see how Knockout loads components via AMD below. Example:
ko.components.register('my-component', { template: { require: 'some/template' }, viewModel: ... });
The returned AMD module object can be in any of the forms allowed for viewmodels. So, it can be a string of markup, e.g. fetched using require.js’s text plugin:
ko.components.register('my-component', { template: { require: 'text!path/my-html-file.html' }, viewModel: ... });
… or any of the other forms described here, though it would be unusual for the others to be useful when fetching templates via AMD.
As well as (or instead of) template and viewModel, your component configuration object can have arbitrary other properties. This configuration object is made available to any custom component loader you may be using.
If your component configuration has a boolean synchronous property, Knockout uses this to determine whether the component is allowed to be loaded and injected synchronously. The default is false (i.e., forced to be asynchronous). For example,
ko.components.register('my-component', { viewModel: { ... anything ... }, template: { ... anything ... }, synchronous: true // Injects synchronously if possible, otherwise still async });
Why is component loading normally forced to be asynchronous?
Normally, Knockout ensures that component loading, and hence component injection, always completes asynchronously, because sometimes it has no choice but to be asynchronous (e.g., because it involves a request to the server). It does this even if a particular component instance could be injected synchronously (e.g., because the component definition was already loaded). This always-asynchronous policy is a matter of consistency, and is a well-established convention inherited from other modern asynchronous JavaScript technologies, such as AMD. The convention is a safe default — it mitigates potential bugs where a developer might not account for the possibility of a typically-asynchronous process sometimes completing synchronously or vice-versa.
Why would you ever enable synchronous loading?
If you want to change the policy for a particular component, you can specify synchronous: true on that component’s configuration. Then it might load asynchronously on first use, followed by synchronously on all subsequent uses. If you do this, then you need to account for this changeable behavior in any code that waits for components to load. However, if your component can always be loaded and initialized synchronously, then enabling this option will ensure consistently synchronous behavior. This might be important if you’re using a component within a foreach binding and want to use the afterAdd or afterRender options to do post-processing.
Prior to Knockout 3.4.0, you might need to use synchronous loading to prevent multiple DOM reflows when including many components simultaneously (such as with the foreach binding). With Knockout 3.4.0, components use Knockout’s microtasks to ensure asynchronicity, and so will generally perform as well as synchronous loading.
When you load a viewmodel or template via require declarations, e.g.,
ko.components.register('my-component', { viewModel: { require: 'some/module/name' }, template: { require: 'text!some-template.html' } });
…all Knockout does is call require(['some/module/name'], callback) and require(['text!some-template.html'], callback), and uses the asynchronously-returned objects as the viewmodel and template definitions. So,
Knockout does not call require([moduleName], ...) until your component is being instantiated. This is how components get loaded on demand, not up front.
For example, if your component is inside some other element with an if binding (or another control flow binding), then it will not cause the AMD module to be loaded until the if condition is true. Of course, if the AMD module was already loaded (e.g., in a preloaded bundle) then the require call will not trigger any additional HTTP requests, so you can control what is preloaded and what is loaded on demand.
For even better encapsulation, you can package a component into a single self-describing AMD module. Then you can reference a component as simply as:
ko.components.register('my-component', { require: 'some/module' });
Notice that no viewmodel/template pair is specified. The AMD module itself can provide a viewmodel/template pair, using any of the definition formats listed above. For example, the file some/module.js could be declared as:
// AMD module 'some/module.js' encapsulating the configuration for a component define(['knockout'], function(ko) { function MyComponentViewModel(params) { this.personName = ko.observable(params.name); } return { viewModel: MyComponentViewModel, template: 'The name is <strong data-bind="text: personName"></strong>' }; });
What tends to be most useful in practice is creating AMD modules that have inline viewmodel classes, and explicitly take AMD dependencies on external template files.
For example, if the following is in a file at path/my-component.js,
// Recommended AMD module pattern for a Knockout component that: // - Can be referenced with just a single 'require' declaration // - Can be included in a bundle using the r.js optimizer define(['knockout', 'text!./my-component.html'], function(ko, htmlString) { function MyComponentViewModel(params) { // Set up properties, etc. } // Use prototype to declare any public methods MyComponentViewModel.prototype.doSomething = function() { ... }; // Return component definition return { viewModel: MyComponentViewModel, template: htmlString }; });
… and the template markup is in the file path/my-component.html, then you have these benefits:
© Steven Sanderson, the Knockout.js team, and other contributors
Licensed under the MIT License.
http://knockoutjs.com/documentation/component-registration.html