Knockout 3 to 4 Guide

Overview of the Differences

TKO is a fundamental change in the foundation of the code behind Knockout, representing the reverse engineering, compartmentalization, and reintegration of the original code that dates back to 2010.

With this reengineering of Knockout into discrete pieces, it will be easier to update, build, and test the features of Knockout, as well as put the individual packages that make up Knockout to use in situations outside Knockout itself.

To see the differences, have a look at the CHANGELOG.md.

Version Comparison

Version tko.js Knockout 4 Knockout 3.x
Browser compatibility Modern browsers (i.e. Proxy support) Internet Explorer 9+ Internet Explorer 6+
ko.BindingHandler Yes Yes No
observableArray.length Yes Browsers with Array subclass support More info No
iterable observableArray Yes Browsers with Symbol support No
event throttle / debounce Yes Yes No
ko.Component Yes Yes No
applyBindings returns Promise Yes Yes No
Polyfills included None Up to ES6 Object.prototype.bind
ko.proxy Yes No No

Build System

TKO has a complete build system rewrite and a conversion of the code from a concatenation of source files to discrete ES6 modules with import and export statements. This process has been complex and taken years, with the result being a Knockout with a strong foundation of discrete reusable components.

Knockout 4 and TKO will be maintained at the new monorepo knockout/tko on GitHub. Knockout 3.x will continue to be maintained at knockout/knockout.

TKO now uses standard tools like lerna and yarn, and employs StandardJS for code style — though all the old code is not yet updated to StandardJS yet.

Test System

Knockout 3 used Jasmine 1 to perform tests. Some packages in TKO continue to use Jasmine 1, while others now use Mocha.

Backwards compatibility and 💥 breaking changes

We’ve strived to have backwards compatibility, and the numerous test cases we’ve run it through have been successful. A key value of Knockout has been and remains the comprehensive test cases that enforce performance to a predictable standard, and TKO meets all the tests that were enforced on Knockout 3.x, notwithstanding a few issues:

  • The function no longer works in data-bind, having been replaced by lambdas, so data-bind='click: function () { x++ }' will have to be replaced by e.g. data-bind='click: => x++'
  • The data-bind is now interpreted by a LL compiler (based on knockout-secure-binding), so some edge case parsing issues may crop up

Browser support

Knockout 4 and TKO and the underlying packages will start support from IE9 forward. We aim to test Knockout across all browsers from this point forward.

If you need IE6, 7, and 8 support you may prefer to keep using the Knockout 3.x line, or be prepared for workarounds or missing functionality.

Options

  • (options) Allow importing ko in node
  • various new options

Utilities

  • (utils) Several array utilities use native functions now (arrayPushAll, arrayFilter, arrayGetDistinctValues, arrayFirst, arrayIndexOf)

  • The throttle and debounce utilities now pass arguments to the target functions

  • utils

    • utils.domNodeDisposal is now exposed as domNodeDisposal
    • arguments to setHtml that are functions are called (not just observables)
    • cleanExternalData now exposed in domNodeDisposal.otherNodeCleanerFunctions
  • error handling

    • onError no longer throws if overloaded; default function is to re-throw.
    • error is thrown when an extender is not found

Life Cycles

  • (internal) Add the ES6 LifeCycle class (see tko.lifecycle)

Observables

  • (observable) When supported, observable.length will now be undefined (was 0 before), and observableArray.length will now be the length of the wrapped array
  • (observableArray) observableArray is now iterable (has a Symbol.iterator property)
  • (subscribable) Add the once, then, when, yet, and next functions

Components

  • (components) Warn with custom-element names that cannot be used with custom elements re. #43 & knockout/knockout#1603
  • (components) Add ko.Component, an abstract base class that simplifies the Component creation and registration API (see tko.utils.component/src/ComponentABC.js)
  • (components) Add getBindingHandler(key) to use binding handlers local to a component

Components can control their binding handlers.

ko.Component example

See the Pen Commented ko.Component by Knockout (@Knockout) on CodePen.

Bindings

  • (binding) ko.applyBindings now returns a Promise that resolves when bindings are completed

  • (binding handlers) Add new-style ES6 Binding Handler class (see custom-bindings documentation and tko.bind/src/BindingHandler.js), descended from the LifeCycle class

  • (bind) String errors on binding are now propagated

Binding Handlers and ko.BindingHandler

In TKO, binding handlers can be either an object, a function or a descendant of ko.BindingHandler. The following are all valid binding handlers:

class NewHandler extends ko.BindingHandler {
    constructor ({$element, valueAccessor, allBindings, $context, onError}) {
        /*
          this.value is valueAccessor()
          this.value(x) is valueAccessor(x);
          if valueAccessor() is observable or a property, it will be set properly.
        */
    }

    get controlsDescendants () { return true|false }

    get bindingComplete () {
        /* overload where binding is asynchronous. See e.g. Component binding*/
    }
    static get allowVirtualElements () { return true|false }
}

handler_fn = (element, valueAccessor, allBindings, viewModel, bindingContext) => { /* ... */ }

the_knockout_3_way = {
    init (element, valueAccessor, allBindings, viewModel, bindingContext) {
        /* ... */
    },

    update (element, valueAccessor, allBindings, viewModel, bindingContext) {
        /* ... */
    },

    allowVirtualElements: true|false
}

// Virtual Elements may be permitted for a binding explicitly.
virtualElements.allowedBindings[handler_name] = true|false

Binding handlers can be registered in the following ways:

// Globally, where NewHandler is a BindingHandler descendant.
NewHandler.registerAs('handler')

// Globally, for any handler type.
ko.bindingHandlers.set(handler_name: handler)

// Knockout 3.x style
ko.bindingHandlers[handler_name] = handler

// For a component
class Component {
    getBindingHandler(bindingKey) {
        if (bindingKey === handler_name) { return handler }
        // Alternatively:
        // return { bindingKey[bindingKey]: handler }[bindingKey]
    }
}

Parsing

  • (parser) Fix interpretation of unicode characters as identifiers / variables
  • == and === use === for comparison (same for != and !==); fuzzy equality ~== / ~!= for the evil twins
  • Knockout 4 will work with a Content Security Policy that prohibits unsafe-eval.
    • add “naked” => lambdas (even in legacy browsers e.g. data-bind='click: => was_clicked(true)'
    • inline functions are no longer supported (e.g. data-bind='click: function (){...}' will fail)
    • No longer uses with statements
    • No longer uses eval/new Function

Namespacing

  • incorporate punches namespacing i.e. data-bind='event.click: => thing(true)' is equivalent to data-bind='event: {click: => thing(true)}'

Attribute Interpolation

Text Interpolation

  • incorporate punches {{ }} and {{{}}} text and attribute interpolation

Event

  • (event binding) Add object-based event handler e.g. event.click: { handler: fn, once: true, capture: true, bubble: false, passive: false}. Also, bubbling can be prevented with event.click: {bubble: false } re #32
  • (event binding) Add throttle and debounce parameters

With

  • (with binding) Fix dependency count for function-arguments [knockout/knockout#2285]

Attr

  • (attr) Support namespaced attributes with attr binding #27

Foreach

  • Use tko.binding.foreach for the foreach binding (based on brianmhunt/knockout-fast-foreach)
  • Add each as an alias of foreach
  • Rewritten as O(1)
  • (foreach binding) When using the as parameter, the $data remains unchanged (i.e. the context inside a foreach is no longer a “child” context, but an extension of the current context); this deprecates the noContext parameter
  • (foreach binding) Expose the conditional on the domData for use by the else binding (when the array is empty, the else binding will be rendered)
  • (foreach binding) Expose $list inside the foreach
  • (foreach binding) Allow noIndex as a peer binding parameter (e.g. foreach: items, noIndex: true)
  • (foreach) Preserve focus when items are deleted and re-added (i.e. moved) in the same animation frame.

Template

  • Make the template binding expose a conditional for else-binding
  • support template literals (``) in bindings (even in legacy browsers)
  • add the @ prefix operator that calls/unwrap functions (i.e. obs()() is the same as @obs)

If / Else

  • add <!-- else --> inside the if binding, and add an else binding (following the brianmhunt/knockout-else plugin)
  • Add else binding
  • Add unless binding

Using

Hidden

  • add hidden binding (knockout/knockut#2103)

HTML

The HTML binding now works in virtual elements. Based on ko.punches.

Filtering

Filters can be applied to modify values in bindings. Based on ko.punches.

Deprecated

  • Template binding options are deprecated
  • expressionWriting (twoWayBinding)
  • ‘.’ in binding handler names
  • jsonExpressionRewriting (expressionRewriting)
  • form parsing
  • bind shim
  • ko.utils.parseJson
  • getFormFields
  • fieldsIncludedWithJsonPost
  • postJson