Knock Me Out

Thoughts, ideas, and discussion about Knockout.js

Knockout.js 3.3 Released

| Comments

Knockout 3.3 is available now! This release features some nice refinements to the components functionality along with a number of other small enhancements and bug fixes. The full release notes can be found here.

In this release cycle, we welcomed Brian Hunt to the core team and he has brought some great energy and contributions to the project. Steve and Michael once again made the bulk of the major changes in this release along with a number of other pull requests from the community.

Components refinements

We received lots of great feedback regarding Knockout’s component functionality. Many developers seem to be having great success using this style of development. For 3.3, we focused on a few low-level enhancements that should provide some increased flexibility.

Synchronous Flag

Components were always rendered asynchronously previously, even when the template/viewModel were already cached. Now, the component registration can include a “synchronous” flag to indicate that the component should render synchronously, if it can. In the case where you have many nested components, this can help alleviate issues with reflows and/or flickering while rendering. The syntax would look like:

1
2
3
4
5
ko.components.register("my-component", {
    template: "<div data-bind=\"text: firstName\"></div>",
    viewModel: { require: "myModule" },
    synchronous: true
});

Working with component child nodes

In Knockout 3.3, you now have options for working with the child nodes contained inside of a component. There are three enhancements that go together targetting this area:

1- The template binding can accept an array of DOM nodes directly via a nodes option

2- If you are using a createViewModel function for your component, it will now receive the child nodes and you can determine how to expose them on your component’s view model for binding against the template binding

1
2
3
4
5
6
7
8
9
10
11
12
13
14
ko.components.register("accordion-item", {
    template: "<h2 data-bind=\"template: { nodes: headerNodes }\"></h2><div data-bind=\"template: { nodes: bodyNodes }\"></div>",
    viewModel: {
        createViewModel: function(params, componentInfo) {
            return {
                data: params.data,
                // grab the first node (as an array) for the header
                headerNodes: componentInfo.templateNodes.slice(0, 1),
                // grab any additional nodes for the body
                bodyNodes: componentInfo.templateNodes.slice(1)
            };
        }
    }
});

3- There is a new $componentTemplateNodes context variable that contains the nodes directly. In normal cases, this will allow you to avoid exposing DOM nodes in your view model and bind in the template directly.

For example, a component template might simply want to add some wrapping markup like:

1
<div class="my-component" data-bind="template: { nodes: $componentTemplateNodes, data: data }"></div>

You could then add the component to your page:

1
2
3
4
5
<my-component params="{ data: item }">
    <h1>My Data</h1>
    <p>Body one</p>
    <p>Body two</p>
</my-component>

If would still be possible to manipulate (slice) $componentTemplateNodes directly in the binding, depending on the complexity of the markup/scenario.

$component context variable

Sometimes inside of a component’s template, when looping through nested structures, you may want to bind up to the root of the component. Rather than trying to manage $parents[x], you can now use $component to get the nearest components root. This is a similar concept to $root for the entire application, but is specific to a component.

Other Enhancements

A few other enhancements that I think are interesting:

awake/sleep notifications from pure and deferred computeds

You can now subscribe to notifications from a pureComputed for when it wakes up and for when it goes to sleep (when nothing depends on it). Additionally, a deferred computed now notifies when it wakes up as well. The subscriptions would look like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
this.fullName = ko.pureComputed(function() {
    return this.firstName() + " " + this.lastName();
}, this);

this.fullName.subscribe(function(value) {
    console.log("fullName is awake with a value: " + value);
}, this, "awake");

this.fullName.subscribe(function() {
    console.log("fullName is sleeping" + value);
}, this, "sleep");

this.fullPhoneNumber = ko.computed(function() {
    return this.areaCode() + " " this.phoneNumber();
}, this, { deferEvaluate: true });

this.fullPhoneNumber.subscribe(function(value) {
    console.log("fullPhoneNumber is awake with a value: " + value);
}, this, "sleep");

Exposing a few additional functions to the release build

  • ko.ignoreDependencies(callback, callbackTarget, callbackArgs) - executes a function and ignores any dependencies that may be encountered. This can be useful sometimes in a computed observable or in a custom binding when you want to execute code, but not trigger updates when any dependencies from that code change. You would use it like:
1
ko.ignoreDependencies(this.myAfterUpdateHandler, this, [true]);
  • ko.utils.setTextContent(element, textContent) handles cross-browser setting the text of a node and handles virtual elements

Fixes

3.3 also includes a number of nice fixes and performance improvements listed here. This includes making the css binding work properly with SVG elements, which has been a long-standing issue.

Please check out Knockout 3.3.0 today! It is available from GitHub, the main site, Bower (bower install knockout), and NPM (knockout).

Comments