KnockoutJS Observables
Observable Basics
// Simple observable
var firstName = ko.observable('John');
console.log(firstName()); // 'John'
firstName('Jane');
console.log(firstName()); // 'Jane'
// Observable array
var items = ko.observableArray([1, 2, 3]);
items.push(4);
console.log(items()); // [1, 2, 3, 4]
// Computed observable
var fullName = ko.computed(function() {
return firstName() + ' ' + lastName();
});
Checkout Observables
// Magento checkout uses observables extensively
var shippingMethod = ko.observable();
var paymentMethod = ko.observable();
var isShippingLoading = ko.observable(false);
// Observable for selected address
var selectedShippingAddress = ko.observable({
firstname: 'John',
lastname: 'Doe',
street: ['123 Main St'],
city: 'New York',
country_id: 'US',
region_id: 43,
postcode: '10001',
telephone: '555-1234'
});
// Computed: check if form is valid
var isFormValid = ko.computed(function() {
var address = selectedShippingAddress();
return address.firstname &&
address.lastname &&
address.street &&
address.city &&
address.postcode;
});
KnockoutJS Bindings
Common Bindings
<!-- text binding -->
<span data-bind="text: customerName"></span>
<!-- value binding -->
<input type="text" data-bind="value: firstName">
<!-- foreach binding -->
<div data-bind="foreach: shippingMethods">
<div class="method">
<input type="radio" data-bind="value: code, checked: $parent.selectedMethod">
<span data-bind="text: title"></span>
<span data-bind="text: price"></span>
</div>
</div>
<!-- visible/if bindings -->
<div data-bind="visible: isSelected()">
<!-- Visible when isSelected is true -->
</div>
<div data-bind="if: hasShippingAddress()">
<!-- Rendered only if condition is true -->
</div>
<!-- click binding -->
<button data-bind="click: placeOrder">Place Order</button>
<!-- css binding -->
<div data-bind="css: { 'active': isActive(), 'loading': isLoading() }">
</div>
<!-- attr binding -->
<input type="text" data-bind="attr: { placeholder: fieldPlaceholder }">
View Models
Checkout View Model
// app/code/Vendor/Checkout/view/frontend/web/js/view/shipping.js
define([
'jquery',
'ko',
'uiComponent',
'Magento_Checkout/js/model/quote',
'Magento_Checkout/js/action/set-shipping-information',
], function ($, ko, Component, quote, setShippingInformationAction) {
'use strict';
return Component.extend({
defaults: {
template: 'Vendor_Checkout/shipping'
},
/** Initialize component */
initialize: function () {
this._super();
// Subscribe to quote changes
quote.shippingAddress.subscribe(this.onAddressChange.bind(this));
return this;
},
/** Observable properties */
shippingMethods: ko.observableArray([]),
selectedMethod: ko.observable(),
isLoading: ko.observable(false),
/** Methods */
getShippingMethods: function () {
return this.shippingMethods();
},
selectMethod: function (method) {
this.selectedMethod(method.code);
this.setShippingMethod();
},
setShippingMethod: function () {
var self = this;
self.isLoading(true);
setShippingInformationAction().done(function () {
self.isLoading(false);
// Navigate to next step
});
},
onAddressChange: function (address) {
this.loadShippingMethods(address);
},
loadShippingMethods: function (address) {
var self = this;
$.ajax({
url: '/rest/V1/carts/mine/shipping-methods',
data: JSON.stringify({ address: address }),
success: function (methods) {
self.shippingMethods(methods);
}
});
}
});
});
Checkout Components
Component Registration
// Register component in checkout
require(
['Magento_Checkout/js/model/checkout-layout'],
function (checkoutLayout) {
checkoutLayout.registerComponent(
'customStep',
{
component: 'Vendor_Checkout/js/view/custom-step',
sortOrder: 2,
template: 'Vendor_Checkout/custom-step'
}
);
}
);
Checkout Provider
// Checkout data provider
define([
'jquery',
'uiComponent',
'Magento_Checkout/js/checkout-data'
], function ($, Component, checkoutData) {
return Component.extend({
defaults: {
listens: {
'${$.shippingAddressForm}:value': 'handleShippingAddressChange'
}
},
initObservable: function () {
this._super()
.observe({
shippingAddress: null,
paymentMethod: null
});
return this;
},
handleShippingAddressChange: function (address) {
this.shippingAddress(address);
checkoutData.setShippingAddressFromData(address);
}
});
});
Template Example
<!-- Vendor/Checkout/view/frontend/web/template/shipping.html -->
<div class="shipping-step" data-bind="visible: isVisible()">
<h2>Shipping Information</h2>
<div data-bind="foreach: getShippingMethods()">
<div class="shipping-method" data-bind="click: $parent.selectMethod">
<input type="radio" data-bind="checked: $parent.selectedMethod, value: code">
<span data-bind="text: carrier_title"></span>
<span data-bind="text: method_title"></span>
<span data-bind="text: $parent.formatPrice(price)"></span>
</div>
</div>
<div data-bind="visible: isLoading()">
<span>Loading shipping methods...</span>
</div>
</div>
Quiz
1. What does ko.observable() create?
2. What does the 'foreach' binding do?
3. What is a computed observable?
Flashcards
Question
What is ko.observable()?
Click to reveal answer
Answer
Creates a reactive property that notifies subscribers when changed
Question
What is a computed observable?
Click to reveal answer
Answer
Auto-recalculates when any of its dependent observables change
Question
data-bind='foreach' does what?
Click to reveal answer
Answer
Renders child elements for each item in an array
Question
How to subscribe to observable changes?
Click to reveal answer
Answer
observable.subscribe(function(newValue) { ... })
Revision Notes
Key Takeaways
- 1. Observables create reactive properties that auto-update UI on change
- 2. Computed observables derive values from other observables automatically
- 3. View models extend uiComponent and use knockout bindings in templates
- 4. foreach binding renders array items as child elements
- 5. Magento checkout components use observables for state management
Interview Tips
- • Explain the difference between observable, observableArray, and computed
- • Describe how KnockoutJS handles two-way data binding in forms
- • Discuss the component initialization flow: defaults → initialize → initObservable
Cheat Sheet
KnockoutJS:
ko.observable(val) → reactive property
ko.observableArray([]) → reactive array
ko.computed(fn) → auto-recalculates
Bindings:
text, value, checked → content
foreach, if, visible → flow control
click, event → interactions
css, attr → styling
View Model:
extend uiComponent
template: 'path/to/template'
initialize() → this._super()