Categories


Archives


Recent Posts


Categories


Knockout Observables for Javascript Programmers

astorm

Frustrated by Magento? Then you’ll love Commerce Bug, the must have debugging extension for anyone using Magento. Whether you’re just starting out or you’re a seasoned pro, Commerce Bug will save you and your team hours everyday. Grab a copy and start working with Magento instead of against it.

No Frills Magento Layout is the only Magento front end book you'll ever need. Get your copy today!

The concept of “observables” can be a little tricky to wrap your head around in Knockout.js. Today we’re going to have a quick tutorial on how observables work outside the context of a normal Knockout.js view. We need to do this because Magento 2’s javascript frameworks make heavy use of observables that goes above and beyond what a normal front end developer needs to be aware of.

If you’re trying to think rationally about your Magento systems, you’ll not only need to understand how observables work, but you’ll often need to know how the internals are implemented.

What are Observables

Observables are stand-alone setter/getter objects. From a Magento bootstrapped page, run the following code in your browser’s javascript console. You should also be able to do this outside of Magento in systems that use a global ko variable instead of Magento’s special RequireJS module.

//load the Knockout.js library -- normally you'd do this in a `define`
ko = requirejs('ko');

//create the observable object with a default value
var objectToHoldMyValue = ko.observable('default value');

//fetch that value by calling the observable as a function
console.log( 'Value: ' + objectToHoldMyValue() );
"default value"

//set a new value by calling the observable as a function with an argument
objectToHoldMyValue('new value')

//fetch that value by calling the observable as a function
console.log( 'Value: ' + objectToHoldMyValue() );
"new value"    

As you can see from the above code and comments, the first job of an observable object is to store a value, return a value, and change a stored value. The syntax may be a little weird if you’re not used to the “objects can be anonymous functions” nature of javascript, but this is nothing too crazy. Also — nothing too necessary either, until you consider subscribers.

//subscribe to a change
objectToHoldMyValue.subscribe(function(newValue){
    console.log("The subscriber sees: " + newValue);
});     

The above code sets up a callback that is, in other terms, an event listener (i.e. you’re subscribing to an event). The event you’re subscribing to? A change in value of the observable. If you run the value setting code again.

objectToHoldMyValue('a second new value')
The subscriber sees: a second new value

you’ll see Knockout calls your subscriber method.

Important: Subscribers are only called when a value changes. If you pass in the observable’s current value, Knockout will not call subscriber callbacks

objectToHoldMyValue('a third new value')
The subscriber sees: a third new value

objectToHoldMyValue('a third new value')
[no output, because the subscriber was not called]

While our example is a little silly, in a real program observables let you take actions whenever the value of a variable changes. That’s an incredibly powerful feature.

The Importance of Observables

Observables are what enables Knockout’s “update the model, automatically update the UI” behavior. If you consider a simple Knockout.js data binding (from the official intro tutorial)

<input data-bind="value: firstName" ... />

Behind the scenes, the value data-binding will check if firstName is an observable. If firstName is an observable, the value binding implementation will setup a subscriber that updates the <input/>. This means whenever a programmer updates the value stored in firstName, the binding’s subscriber runs, and the <input/>‘s value is updated.

Knockout.js does all this behind the scenes. Even if you create a custom binding, Knockout handles setting up the subscriber, and your binding’s update method gets called. There’s no need for you, as a binding developer, to know about subscribers.

The subscribe method feels like something that should be a private API, but since this is javascript and everything’s public by default, developers can and will setup their own subscribers for observables.

Something else that may cause you, as a javascript or PHP programmer, a bit of cognitive dissonance is the lack of empty parameter () parenthesis when someone uses an observable in a data binding

<input data-bind="value: firstName" ... />

When I first started with Knockout.js, the lack of any clear distinction between a regular object property and an observable — at the template level — was a little confusing. Once you understand that observables are just callable javascript objects, and understand that the binding needs to receive this object and not its stored value, things start to make a little more sense. Developers from a civilized language (like ruby), where you don’t need parenthesis to call a method, are now free to laugh.

For Magento 2 Developers

As a Knockout.js developer, you can live a life that’s mostly ignorant of how observables are implemented. Magento 2 developers don’t have this luxury. The UI Component systems make heavy use of observable properties, and also setup their own subscribers.

The good news is: When you see something like

//...
someProp: ko.observable('default value')
//...

you don’t need to panic. The program is just using someProp to store a value.

The bad news is — that observable may have a number of subscribers. These subscribers may come from a Knockout.js template’s data-bind attributes. They may come from Magento core code setting up their own subscribers. You can view the number of callbacks an observer has via the _subscriptions property

console.log(objectToHoldMyValue._subscriptions);
Object
    change: Array[3]
        0: ko.subscription
        1: ko.subscription
        2: ko.subscription

Or peek at a particular callback like this

console.log(
    objectToHoldMyValue._subscriptions.change[1].callback
);

However — you’re at the mercy of your debugger w/r/t to how this information is displayed, and there’s no easy way to tell where a particular subscriber comes from. Also, we’re deep into Knockout.js internals at this point, and relying on this sort of code for anything other than debugging introduces enormous potential for instabilities.

Series Navigation<< The Curious Case of Magento 2 MixinsModifying a jQuery Widget in Magento 2 >>