Skip to content
intermediate Phase 55 · Frontend JavaScript

KnockoutJS Deep Dive

Advanced KnockoutJS in Magento 2: observables, computed, bindings, components, and custom bindings

1h
0 problems
Topic Progress 0%

Observables

Basic Observables

define(['knockout'], function(ko) {
    'use strict';
    
    return function() {
        this.name = ko.observable('John');
        this.age = ko.observable(25);
        this.isActive = ko.observable(true);
        this.items = ko.observableArray([]);
    };
});

Observable Operations

// Get value
var name = this.name();

// Set value
this.name('Jane');

// Array operations
this.items.push({ id: 1, name: 'Item 1' });
this.items.unshift({ id: 0, name: 'Item 0' });
this.items.pop();
this.items.shift();
this.items.splice(0, 1);
this.items.remove(function(item) {
    return item.id === 1;
});
this.items.removeAll();

// Check if observable
ko.isObservable(this.name); // true
ko.isObservable(this.items); // true

Observable Arrays

this.products = ko.observableArray([
    { id: 1, name: 'Product 1', price: 29.99 },
    { id: 2, name: 'Product 2', price: 39.99 }
]);

// Subscribe to changes
this.products.subscribe(function(newArray) {
    console.log('Products changed:', newArray);
});

// Get underlying array
var rawArray = this.products();

Computed Observables

Basic Computed

define(['knockout'], function(ko) {
    'use strict';
    
    return function() {
        this.firstName = ko.observable('John');
        this.lastName = ko.observable('Doe');
        
        // Computed from observables
        this.fullName = ko.computed(function() {
            return this.firstName() + ' ' + this.lastName();
        }, this);
    };
});

Computed with Dependencies

this.cart = ko.observableArray([]);
this.taxRate = ko.observable(0.1);

this.subtotal = ko.computed(function() {
    return this.cart().reduce(function(sum, item) {
        return sum + (item.price * item.quantity);
    }, 0);
}, this);

this.tax = ko.computed(function() {
    return this.subtotal() * this.taxRate();
}, this);

this.total = ko.computed(function() {
    return this.subtotal() + this.tax();
}, this);

Pure Computed

// More efficient - only re-evaluates when dependencies change
this.pureComputed = ko.pureComputed(function() {
    return this.firstName() + ' ' + this.lastName();
}, this);

Computed Best Practices

  • Use pureComputed for better performance
  • Avoid side effects in computed
  • Keep computeds short and focused
  • Don't write to observables in computed

Bindings

Text Binding

<span data-bind="text: name"></span>
<span data-bind="text: fullName()"></span>

HTML Binding

<div data-bind="html: richContent"></div>

Attribute Binding

<a data-bind="attr: { href: url, title: titleText }">Link</a>
<img data-bind="attr: { src: imageUrl, alt: imageAlt }">

CSS Bindings

<div data-bind="css: { 'active': isActive, 'error': hasError }"></div>
<div data-bind="css: cssClass"></div>

Style Binding

<div data-bind="style: { color: textColor, backgroundColor: bgColor }"></div>

Visibility Binding

<div data-bind="visible: isLoggedIn">Welcome!</div>
<div data-bind="if: showDetails">
    <p>Details here</p>
</div>

Loop Binding

<ul data-bind="foreach: items">
    <li>
        <span data-bind="text: name"></span>
        <span data-bind="text: price"></span>
    </li>
</ul>

Value Binding

<input type="text" data-bind="value: name">
<input type="checkbox" data-bind="checked: isActive">
<select data-bind="options: options, value: selectedOption"></select>

Event Binding

<button data-bind="click: handleClick">Click Me</button>
<input data-bind="value: inputVal, valueUpdate: 'afterkeydown'">
<form data-bind="submit: handleSubmit"></form>

Custom Bindings and Components

Custom Binding

define(['knockout'], function(ko) {
    'use strict';
    
    ko.bindingHandlers.tooltip = {
        init: function(element, valueAccessor) {
            var value = ko.unwrap(valueAccessor());
            
            $(element).tooltip({
                title: value
            });
            
            ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
                $(element).tooltip('destroy');
            });
        },
        update: function(element, valueAccessor) {
            var value = ko.unwrap(valueAccessor());
            $(element).tooltip('setTitle', value);
        }
    };
});

Usage

<span data-bind="tooltip: 'This is a tooltip'">Hover me</span>

Magento Components

define([
    'uiComponent'
], function(Component) {
    'use strict';
    
    return Component.extend({
        defaults: {
            template: 'Vendor_Module/component'
        },
        
        initialize: function() {
            this._super();
            this.name = ko.observable('');
            return this;
        },
        
        getName: function() {
            return this.name();
        }
    });
});

Component Template

<div data-bind="scope: getName()">
    <input type="text" data-bind="value: name">
    <span data-bind="text: name"></span>
</div>

Quiz

1. What is a KnockoutJS observable?

Question 1 options

2. What binding shows/hides elements based on visibility?

Question 2 options

3. What is a computed observable?

Question 3 options

Flashcards

Question

What is a KnockoutJS observable?

Answer

A function that tracks changes and notifies subscribers

Question

How do you create a computed observable?

Answer

ko.computed(function() { return ...; }, context)

Question

What binding loops through arrays?

Answer

foreach binding

Question

How do you bind to attributes?

Answer

attr: { href: url, title: text }

Question

What is the 'if' binding?

Answer

Renders element only if condition is true

Revision Notes

Key Takeaways

  • 1. Observables track changes and notify subscribers
  • 2. Computed observables derive from other observables
  • 3. Bindings connect HTML to JavaScript data
  • 4. Custom bindings extend KnockoutJS functionality
  • 5. Magento components extend uiComponent

Interview Tips

  • Explain the observable pattern
  • Know the difference between observable and computed
  • Discuss common bindings and their use cases
  • Be ready to create custom bindings

Cheat Sheet

Observables:
  this.name = ko.observable('value')
  this.items = ko.observableArray([])

Computed:
  this.full = ko.computed(function() {
      return this.first() + ' ' + this.last();
  }, this);

Bindings:
  text, html, attr, css, style, visible
  if, foreach, value, checked, event, click

Custom binding:
  ko.bindingHandlers.name = { init, update }