KnockoutJS cheatsheet for Magento 2

Lucas Calazans
6 min readApr 17, 2023

--

KnockoutJS on Magento works with components, so today, we’ll see some valuable tips we need to know in our daily work.

Scope — Create your component

Starting at the beginning, we need to talk about the scope bind.

This is used when we want to create a Knockout component on Magento.

A Knockout component is a combination of JS and an HTML template. When they’re connected, we can use the JS variables and functions inside the template without much effort.

To create this connection between JS and HTML, we have a bind called scope.

We need to create a JS and an HTML file to accomplish this. I’ll explain each part of the creation of a component (And its files) in the following example.

counter.js:

define([
'uiComponent'
], function(Component) {

return Component.extend({
initialize: function() {
this._super();

console.log("It's working");
},
});
});

I pointed 3 items that are important to note in this example:

  • uiComponent: This is a base component, we use it as a parent to add our logic. This is one of the base components we can use to extend, Magento has more base components, each for different situations.
  • return Component.extend: This is the most important in this script, it extends the parent component. Note that extend is a function, and we have an object passed to it, all the functions added inside this, will override the parent one (Like the initialize function), or we can add a new function that can be used in the HTML or this script.
  • initialize: This is the entry point of your component, so if you need to register some listener or something else that needs to run right after the component load, this is the place you’re looking for. It’s also important to call this._super(), It’ll call the initialize function from the parent component, in this case, the uiComponent.

counter.phtml:

<div data-bind="scope: 'counter'">
<span>counter component</span>
</div>
<script type="text/x-magento-init">
{
"*": {
"Magento_Ui/js/core/app": {
"components": {
"counter": {
"component": "LucasCalazans_Counter/js/view/counter"
}
}
}
}
}
</script>

For this phtml, we have 2 things to point out:

<script type="text/x-magento-init">

This is where the magic happens on Magento, a Magento-specific script we must use to register our new component.

  • First, we need to declare the script with this type text/x-magento-init, Magento will find all the scripts with this type and register it when the page loads.
  • Second, we add a JSON inside this script, yes, it’s a JSON, so you need to use double quotes instead of single ones.
  • The JSON starts with an asterisk, but it could be a CSS selector for the container that will be your component’s template. In this case, I just used the asterisk to be more generical.
  • Magento_Ui/js/core/app — The app path is really important, this is a base component used to register all the components in the store.
  • components: Used to say to the app component that all the entries below will be new components.
  • counter: This is the name of our new component, it can be everything you want, just remember that it’s not allowed to use spaces and special characters.
  • We need to say the path for our JS file, so I indicated it as LucasCalazans_Counter/js/view/counter (We don’t use the .js extension here).

<div data-bind="scope: 'counter'">

Now we need to connect the registered component with a template. For that, we use the scope binding, the parameter sent to the scope is the name that we choose inside the script that we just saw.

And done, now you should be able to use your HTML and JS as a KnockoutJS component.

Observables

Observables are the base of KnockoutJS, there are observables everywhere in Magento, let’s understand their purpose and usage.

We can compare an observable with a variable, you can change its value whenever you want, but the difference is that you can observe its changes, and it’s reactive on where it’s been used.

As a quick example, if you’re using an observable, let’s say the subtotal of the cart, in a span and change its value via javascript, the span that is using the subtotal observable will change the text automatically without the need to search the element (span) via DOM and updating its value.

The same can occur on the other side, we can have an observable as a value of an input, and we can observe its value via javascript, so if the user changes the value on the input, the Knockout can call your callback, and you’ll receive the new value that was changed. I’ll explain more about this in the Subscribe section.

How to manipulate an Observable

First, we need to create the observable, for that, we need to use the ko.observable() method:

this.count = ko.observable(0);

The observable function accepts one parameter which you can pass the initial value of your observable.

Get the observable value

Now that you created your observable, your this.count variable will be a function, so if you want to get the value, you’ll need to call the function, and it’ll return the current value:

const currentValue = this.count();

Updating the value

As we saw, this.count is a function, so you can’t simply attribute the new value using the conventional way: this.count = currentValue + 1.

To update an observable value you need to pass the new value as an attribute:

this.count(currentValue + 1);

After this code, the count observable will now have a different value. When you update the value, it’ll dispatch an event to all the places that are using this

Example

Let’s see a complete example to explain it in a better way.

We’re going to create an observable called count and two events to increase and decrease its value.

The observable is used in the HTML using the text data-bind, and it’s also updated via JS in the increase and decrease methods

<div data-bind="scope: 'counter'">
<button data-bind="click: decrease">-</button>
<span data-bind="text: count"></span>
<button data-bind="click: increase">+</button>
</div>

count.js

define([
'ko',
'uiComponent',
], function(ko, Component) {
return Component.extend({
defaults: {
count: ko.observable(0)
},

decrease: function() {
const currentValue = this.count();
this.count(currentValue - 1);
},

increase: function() {
const currentValue = this.count();
this.count(currentValue + 1);
}
});
});

Result

Subscribers

If you want to observe the changes of an observable, you can use the subscribe method.

Basically, every time that the observable changes, it’ll fire your callback, sending the new value as a parameter.

It’s useful when you want to apply some conditions to it.

In the following example, I created a subscribe event in the initialize function, and every time the count changes, it’ll call my countChecker callback. I‘m checking if the count is greater than maxValue, if so, I update the count to be the same as the maxValue, and then I show a message using the console.warn.

defaults: {
count: ko.observable(0),
maxValue: 5,
},
initialize: function() {
this._super();

this.count.subscribe(this.countChecker.bind(this));
},

countChecker: function(count) {
if (count <= this.maxValue) return;

this.count(this.maxValue);
console.warn('You\'ve reached the maximum value of the counter');
},

Create your custom binding

Knockout already has many bindings that help us create our layouts, eg:

  • visible
  • text
  • html
  • class

In addition, Magento also added its bindings, like i18n.

In the next block of code, I’m creating a new binding called onlyNumbers, it can be used if we want an input that only accepts numbers.

define([
'ko',
], function(ko) {
const parseNumber = function(event) {
const element = event.target;
element.value = element.value.replace(/\D*/gm, '');
}

ko.bindingHandlers.onlyNumbers = {
init: function(element, valueAccessor) {
element.removeEventListener('keyup', parseNumber);

const bindingValue = valueAccessor();
if (!bindingValue) return true;

element.addEventListener('keyup', parseNumber);

return true;
},
};

return true;
});

Now, when we need to use this new binding, we can use it like this:

<input type="text" data-bind="onlyNumbers: true" />

Bonus Tip

Retrieve and identify component data from your browser

Have you ever had to find out which component is responsible for a part of the page?

You may have used the find tool of your IDE using some class or something like this.

Using requirejs and the correct Knockout functions, we can get its data only using the devtools console.

To do this, you need to:

  • Inspect the element that you want to discover
  • Let the element selected on the Elements tab
  • Copy/Paste one of the following lines:
require('ko').contextFor($0).$data
require('ko').contextFor($0).$data.component
require('ko').contextFor($0).$data.template
How to identify the component using the Browser

Wrap-up

I hope that the information provided has been easily understandable. But if you have any questions, please feel free to leave a comment below and provide feedback. You can also find my social media profiles listed below if you wish to stay updated or get in touch with me.

https://www.linkedin.com/in/lucas-calazans/

--

--

Lucas Calazans
Lucas Calazans

No responses yet