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?
2. What binding shows/hides elements based on visibility?
3. What is a computed observable?
Flashcards
Question
What is a KnockoutJS observable?
Click to reveal answer
Answer
A function that tracks changes and notifies subscribers
Question
How do you create a computed observable?
Click to reveal answer
Answer
ko.computed(function() { return ...; }, context)
Question
What binding loops through arrays?
Click to reveal answer
Answer
foreach binding
Question
How do you bind to attributes?
Click to reveal answer
Answer
attr: { href: url, title: text }
Question
What is the 'if' binding?
Click to reveal answer
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 }