15 min read

(For more resources related to this topic, see here.)

Subscribables

When creating our Inventory Management app, it was easy to see that a Knockout Observable is one of the core objects that Knockout was built around. However, under the covers of Observables, Observable Arrays, and Computed Observables is Subscribable. A Subscribable is simply an object that has three methods and an array of Subscriptions. The three methods are:

  • subscribe: This adds a Subscription callback for a certain topic to be invoked when subscriptions are “notified”. The default topic is “changed”.
  • notifySubscribers: This calls all subscriptions for a certain topic and passes one argument to all the Subscription’s callbacks.
  • extend: This applies an Extender to the Subscribable object.

The following code snippet shows an example of how the first two methods allow Subscribables to perform basic Pub/Sub functionality:

ar test = ko.observable(); // create a subscription for the "test-event" test.subscribe(function (val) { console.log(val); }, test, "test-event"); test.notifySubscribers("Hello World", "test-event");

One of the coolest abilities of a Knockout Subscribable is its ability to “mix-in” to any JavaScript object. The following code example shows a great way that you can leverage the Knockout Subscribable in some of your current code:

// Dummy Subscribable function PubSub(){ // inherit Subscribable ko.subscribable.call(this); } // create an instance of our Subscribable var pubsub = new PubSub(); // make a subscription var subscription = pubsub.subscribe(function (val) { console.log(val); }, pubsub, 'test-topic'); pubsub.notifySubscribers("hello world", "test-topic"); // console: "hello world" // clean up things subscription.dispose();

Whenever we call the subscribe function of a Subscribable, we are returned a Subscription. Often developers will ignore the Subscription that they are returned from these calls, but it is important to understand that a Subscription allows you to properly dispose of your Subscription’s callback and also your app’s other objects that are referenced by that callback. Just simply calling dispose on your Subscription takes care of this, and you can ensure that your app doesn’t create memory leaks!

Now that you understand one of Knockout’s absolute core building blocks, the Subscribable, we can learn how Knockout extends its usage into other building blocks.

Observables

The Knockout Observable is one of the first objects that leverages the Subscribable functionality, and is one of the most simple, yet powerful pieces of the Knockout library. The following shows the very basic idea of implementation of Observable:

// Very Simple Knockout Observable Implementation // ko.observable is actually a function factory ko.observable = function (initialValue) { // private variable to hold the Observable's value var _latestValue = initialValue; // the actual "Observable" function function observable() { // one or more args, so it's a Write if (arguments.length > 0) { // set the private variable _latestValue = arguments[0]; // tell any subscribers that things have changed observable["notifySubscribers"](_latestValue); return this; // Permits chained assignments } else { // no args, so it's a Read // just hand back the private variable's value return _latestValue; } } // inherit from Subscribable ko.subscribable.call(observable); // return the freshly created Observable function return observable; };

Again, this is very basic example implementation of the Knockout Observable (the actual implementation has much more robust logic, equality checking, and dependency detection). You can see from its implementation that the ko.observable function is essentially a function factory. When you call this factory method, it generates a function (simply called observable) that provides a basic “get/set” API. If you invoke the returned function without an argument, then it returns the _initialValue, but if you invoke the function with an argument, it sets the _initialValue to that argument and notifies all subscribers.

Observables are useful for just about anything you can imagine when building your application as they are event-driven. We can build an application architecture that only worries about reacting to events (such as a button click, or an input element’s change event), rather than a procedural, single entry-point type of code base.

Observable Arrays

I briefly mentioned Observable Arrays earlier, and I promised that I would dig deeper into this concept. While the previous example implementation of the Observable is relatively simple, the Observable Array is even simpler, as shown in the following example implementation:

// Very Simple Knockout Observable Array Implementation // function factory for observable arrays ko.observableArray = function (initialValues) { // make sure we have an array initialValues = initialValues || []; // create a Knockout Observable around our Array var result = ko.observable(initialValues); // add our Observable Array member functions // like "push", "pop", and so forth ko.utils.extend(result, ko.observableArray['fn']); // hand back the Observable we've created return result; };

As you can see, it is really just an Observable, except that the value of Observable is an Array. Functions have also been added to the Observable Array that match methods of a native Array. The Knockout authors have done this in order to allow us as developers to be able to work with an Observable Array in the same way we can work with normal Arrays.

A little known extensibility point is the fn property of the Observable Array constructing function. You can add your own functions to this special property, and they will be included on all Observable Arrays that your application uses. The following implementation shows how we can easily add a function that filters items in the array:

ko.observableArray.fn['filter'] = function (filterFunc) { // get the array var underlyingArray = this(); var result = []; for (var i = 0; i < underlyingArray.length; i++) { var value = underlyingArray[i]; // execute filter logic if (filterFunc(value)) { result.push(value); } } return result; }; var list = ko.observableArray([1, 2, 3]); // filter down the list to only odd numbers var odds = list.filter(function (item) { return (item % 2 === 1); }); console.log(odds); // [1, 3]

The use of a fn property on constructor functions is a common pattern that you see in many libraries (including jQuery), and Knockout is no different. The fn property also exists on the Observable, so you can extend its functionality the same way.

Computed Observables

Computed Observables are arguably the most powerful feature of Knockout. You may have noticed that creating a ViewModel with nothing but Observables and Observable Arrays is a huge step up from plain JavaScript, but it would be really nice to have other properties on your ViewModel that simply depended upon other Observables and Observable Arrays and updated themselves when their dependencies change.

Enter the Computed Observable (or in older versions of Knockout, the dependentObservable). A Computed Observable looks to be very similar to a regular Observable; however instead of holding a value, it contains a function that is used to evaluate the return value of the Computed Observable. The evaluation function is only executed when the Observables that it depends upon change. The following example shows a very basic example of how this works.

var a = ko.observable(1); var b = ko.observable(2); var sum = ko.computed(function () { var total = a() + b(); // let's log every time this runs console.log(total); return total; }); // console: 3 a(2); // console: 4 b(3); // console: 5 b(3); // (nothing logged)

Computed Observables have an extremely interesting and ingenious implementation, which you can dig into on your own. However, there are a few important things to note.

First, Computed Observables build up a list of dependencies (or Subscriptions to be precise) when the evaluation function executes. If you want a certain Observable to be registered as a subscription to your Computed Observable, then you need to ensure that the Observable is always called in your evaluation function. The following example shows how you can set up a changing dependency set for a Computed Observable:

var dep1 = ko.observable(1); var dep2 = ko.observable(2); var skipDep1 = false; var comp = ko.computed(function () { dep2(); // register dep2 as dependency if(!skipDep1) { dep1(); // register dep1 as dependency } console.log('evaluated'); }); // console: evaluated dep1(99); // console: evaluated skipDep1 = true; dep2(98); // console: evaluated dep1(97); // (nothing logged)

Generally, it is a bad idea to do something like the previous example. It is extremely hard to debug, and is not intuitive for other developers to realize what is going on. Instead, the suggested pattern is to retrieve the value of each of your dependencies at the beginning of the evaluation function, and then perform any conditional logic on private scoped variables after that.

Secondly, and this applies to Observables in general, a subscription callback will only be called if the Observable determines that the before and after values are different. Each Observable has a property called equalityComparer which determines if the before and after values of an Observable are in fact different. If you notice in the first example, the very last call to set the value of b did nothing. This is because b was already 3 and had no reason to notify its subscriptions. The default equalityComparer of each Observable is a function that only compares JavaScript primitives. So to make that simple, if a Computed Observable’s dependencies are all objects, complex or not, the evaluator function will be re-executed every time one of its dependencies change. The following example provides an illustration of this important concept:

var depPrimitive = ko.observable(1); var depObj = ko.observable({ val: 1 }); var comp = ko.computed(function () { // register dependencies var prim = depPrimitive(); var obj = depObj(); console.log("evaluated"); }); // console: evaluated depPrimitive(1); // (nothing logged) var previous = depObj(); depObj(previous); // console: evaluated

It is important to understand this because many novice Knockout developers hurt themselves by overusing Computed Observables and accidentally building long event chains that ultimately lead to slow-performing applications.

Lastly, Computed Observables can have both an evaluation function for “setting” and “getting” their values. This is incredibly useful, because it allows us to have private Observable fields on our ViewModel that are protected through publicly exposed “get/set” functions and we also get the power of a normal Observable. In the following code you can see an example of this useful concept:

var _val = ko.observable(1); var vm = { val: ko.computed({ read: function () { return _val(); }, write: function (newVal) { _val(newVal); } }) }; vm.val(2); console.log(_val()); // console: 2 _val(3); console.log(vm.val()); // console: 3

Utilities

Knockout is full of many useful utilities that you can use in your application development. You can find these by exploring the ko.utils namespace, but some of my favorite utilities are as follows:

  • extend: This function “mashes” two objects together. Essentially all of the properties and functions of the second argument to this function call are added to the first argument.

  • unwrapObservable: This function takes an object property and intelligently returns its value, figuring out whether it is a Knockout Observable or just a simple property. It is extremely useful if you have to write functions that might take an object, a primitive, or an Observable as an argument and you need it to figure that out properly at runtime.

  • All of the Array utilities: Knockout has several Array-manipulation functions that allow you to do filtering, mapping, and removing items. I usually add these utilities to the special ko.observableArray.fn property when I first start a project.

The following shows some example usage of these utilities:

// extend usage var a = { val: 1 }, b = { val: 2 }; ko.utils.extend(a, b); console.log(a.val); // console: 2 // unwrapObservable usage var c = ko.observable(99), d = 98; console.log(ko.utils.unwrapObservable(c)); // console: 99 console.log(ko.utils.unwrapObservable(d)); // console: 98 // array "map" utility function usage var arr = [100, 101]; var mapped = ko.utils.arrayMap(arr, function (item) { return item + 50; }) console.log(arr); // console: [ 150, 151 ]

Data-bind statements

So far, we’ve focused on quite a bit of the JavaScript components of the Knockout library. However, Knockout was designed to make binding JavaScript objects and HTML extremely easy. The API that is given to us to use is through the HTML5 compliant data-bind statement.

From the examples in our previous app, you may think that all the data-bind statements are simply matching an HTML element’s attribute to a property of our ViewModel. In actuality, though, the data-bind syntax supports quite a bit more. We can actually implement small bits of JavaScript in these statements, and it will be evaluated correctly at runtime. The data-bind statement also allows declaring a comma-separated list of binding declarations. The following implementation shows some of the allowed syntaxes for data-bind statements on HTML elements:

<span data-bind="text: myText"></span> <div data-bind="visible: isVisible, with: someProp"></div> <input data-bind="value: someVal, css: { 'error': !someVal.isValid(), 'success': someVal.isValid() }"/>

Applying bindings

The applyBindings function is where all of the Knockout magic gets kicked off. Many examples show the applyBindings function being called with a ViewModel object being passed in as the only argument, but you can also specify a DOM node as the second argument. When passing a DOM node as the second argument, Knockout only binds your ViewModel to that node and its children.

Most basic applications are fine with only having one ViewModel, and just simply calling applyBindings with that single ViewModel. However, I’ve built several complex applications where we used multiple ViewModels for one page. It is important to consider the advantages of having a separate ViewModel in your application for, alerts, settings, and current user information. In some cases, you can also gain performance benefits by limiting the number of nodes that Knockout has to walk in order to finish its binding process. If you are only enhancing a small portion of your HTML page with Knockout, don’t call applyBindings blindly across the entire DOM.

Binding handlers

I’ve mentioned Knockout’s amazing extensibility points, and few are better than Knockout’s binding handler objects. Every Knockout binding that you see in a data-bind statement is implemented as a binding handler, and Knockout allows us to define our own binding handlers so that we can implement, or override, our own custom functionality.

With MVVM-style application development, we have two types of bindings—one-way bindings and two-way bindings. One-way bindings are simply read-only bindings, and are for pushing a ViewModel’s property updates through to the DOM. You can probably guess that two-way bindings are bindings that take that one step further and allow DOM changes to be pushed back through to a ViewModel’s property. Knockout allows us to create both of these types of bindings. The following shows a very basic template for a binding handler:

ko.bindingHandlers['myHandler'] = { init: function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext){ }, update: function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext){ } };

As we can see, we are provided two hooks for implementing our own logic—init and update functions that have the same signature. The arguments for those functions are as follows:

  • element: The HTML element that the data-bind statement is declared on.

  • valueAccessor: A function that returns the ViewModel value to which the binding is set. If the binding is set to an Observable property, then the Observable is returned (and we’ll need to unwrap within our logic).

  • allBindingsAccessor: Similar to the valueAccessor, but it returns an object that contains all the bindings and their respective bound values.

  • viewModel: The ViewModel or root object that was passed into the applyBindings statement.

  • bindingContext: A special object that has specific properties that represent the data context of the current binding. The binding context has a $data property that represents the currently bound context (which most often would be same as the ViewModel argument). It also has $parent and $parents properties that represent any data contexts that may be bound to elements higher in the DOM tree. We usually only use the parent properties if we have used the with binding in our application.

You might be wondering, which functions we should implement our custom logic in since they both look to do the same thing. The init function will be called only once when applyBindings function has been called. Knockout walks the DOM, finds data-bind statements, processes them, and calls the init method on each binding handler that is needed. The update function is then called immediately after the init function is called, and then also whenever the value it is bound to changes (if the value is a Subscribable).

My rule-of-thumb for binding handlers is that I register all of my event handlers (change, blur, focus) in the init function, then I perform my HTML manipulation in the update function.

The following code snippet shows a common one-way binding that I add to my projects. It simply reverses the Boolean logic of the visible binding so that I can handle situations where a ViewModel property makes more sense to be developed as vm.isHidden(); instead of vm.isVisible();.

// invisible -> the inverse of 'visible' ko.bindingHandlers['invisible'] = { update: function (element, valueAccessor) { var newValueAccessor = function () { // just return the opposite of the visible flag! return !ko.utils.unwrapObservable(valueAccessor()); }; return ko.bindingHandlers.visible.update(element, newValueAccessor); } };

The following code snippet shows a two-way binding that I use for ensuring I have a valid number. It manually handles the updates from the UI and pushes those to the ViewModel, and then also updates the UI when the ViewModel changes.

// simple number parsing function parseNumber(strVal){ return parseInt(strVal, 10); } // very basic two-way binding handler ko.bindingHandlers['number'] = { init: function (element, valueAccessor, allBindingsAccessor) { //handle the input changing ko.utils.registerEventHandler(element, "change", function () { var observable = valueAccessor(); var number = parseNumber(element.value); if (number !== NaN) { observable(element.value); } }); }, update: function (element, valueAccessor) { var value = ko.utils.unwrapObservable(valueAccessor()); var number = parseNumber(value); if (number !== NaN) { element.setAttribute("value", number); } } };

Summary

All in all you can see that Knockout is a very mature and thorough JavaScript library. In this article you have learned:

  • The core objects of Knockout and their usage

  • Knockout utilities

  • How to create and manage an app with multiple ViewModels

  • Custom Binding Handlers

Knockout has a lot of goodies under the hood. I’ve included many snippets of Knockout’s actual source code in this section so that you won’t be afraid to look through the source code and learn more about how you can take advantage of its APIs.

Resources for Article :


Further resources on this subject:


LEAVE A REPLY

Please enter your comment!
Please enter your name here