How to apply component bindings after ko.applyBind

2019-04-01 18:05发布

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();

2条回答
▲ chillily
2楼-- · 2019-04-01 18:18

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).

查看更多
等我变得足够好
3楼-- · 2019-04-01 18:37

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/

查看更多
登录 后发表回答