Is there a way to apply component bindings after the ko.applyBindings() call?
The point is, I use requireJS to load my modules/components async. So how do I know that all bindings are registered?
Demo JS Fiddle
ko.applyBindings();
ko.components.register('my-component',
{
viewModel: function() {
this.name = ko.observable('My Name');
},
template: '<input type="text" data-bind="value: name"></input>'
}
);
// Moving it here, it works:
// ko.applyBindings();
There are a couple of pieces that you could use to dynamically understand and load components.
1- A custom component loader
You could create a component loader that understands from a component name, which files to require.
For sake of an example, lets say that any component that starts with my-
, we want to require from the components
directory by convention.
It could look like:
//add a loader to the end of the list of loaders (one by default)
ko.components.loaders.push({
getConfig: function(name, callback) {
var widgetName;
//see if this is one of our widgets
if (name.indexOf("my-") > -1) {
widgetName = name.substr(3).toLowerCase();
//provide configuration for how to load the template/widget
callback({
require: "components/" + widgetName
});
} else {
//tell KO that we don't know and it can move on to additional loaders
callback(null);
}
},
//use the default loaders functionality for loading
loadComponent: ko.components.defaultLoader.loadComponent
});
If the default loader can't find a component (not registered yet), then this one would kick in.
2- We would still need to handle custom elements, as these run off of the registration as well. The documentation describes the ko.components.getComponentNameForNode
method that could be overriden to dynamically convert an element tag to component name.
In our case, this could look like:
var existingGetComponentNameForNode = ko.components.getComponentNameForNode;
ko.components.getComponentNameForNode = function(node) {
var tagNameLower = node.tagName && node.tagName.toLowerCase();
//if we found one of our tags, then use it as the component name
if (tagNameLower.indexOf("my-") > -1) {
return tagNameLower;
}
// call the original
return existingGetComponentNameForNode.apply(this, arguments);
};
Here is a fiddle that puts these together with require.js: http://jsfiddle.net/rniemeyer/tgm8wn7n/
Also, take note of the IE6-8 warning here, as it would affect dynamically understanding the custom elements.
Alternatively, you would need to ensure that all of your components are registered before that component is bound in the UI (not necessarily at the time of the initial applyBindings, but as soon as a component is encountered that needs to be bound).
To expand on RP Niemeyer's suggestion to ensure all components are registered before the bindings is hit, I have done this successfully in the past to load components I only need rarely.:
You can ensure that the component binding is not attempted before you have registered the component by using it from within a property of a root or controller style top level model, and wrapping it in a with
binding. KO will not evaluate anything inside the with
until the property is assigned.
This is easier to explain with an example than words!
So here is your code, demonstrating that applyBindings
is called early, then using a timeout to simulate later loading of code relying on the component:
Markup:
<!-- ko with: Container -->
<my-component>replaceme</my-component>
<!-- /ko -->
Code:
function Model()
{
ko.components.register('my-component',
{
viewModel: function() {
this.name = ko.observable('My Name');
},
template: '<input type="text" data-bind="value: name"></input>'
}
);
}
function RootModel()
{
this.Container = ko.observable();
this.load = function()
{
this.Container(new Model());
};
}
var rootmodel = new RootModel();
ko.applyBindings(rootmodel);
setTimeout(function() { rootmodel.load(); }, 1000);
Working Fiddle: http://jsfiddle.net/whelkaholism/dhaox1ae/