Knockout.js Observables — Dependency Tracking and Change Notification
In this tutorial, you will learn about Knockout.js Observables. We cover key concepts, practical examples, and best practices to help you master this topic.
Knockout.js observables are function-based reactive properties that notify all subscribers whenever their value changes, enabling automatic UI synchronization without polling or manual event wiring.
What You'll Learn
- Creating observables with
ko.observable() - Reading and writing observable values
- Understanding the subscriber-notification mechanism
- Chaining write operations and using peek
- When to use writeable observables vs computed values
Why It Matters
Without observables, you would have to manually update the DOM every time data changes. Observables automate this: you change the data, and Knockout figures out which parts of the UI need refreshing and updates only those elements.
Real-World Use
A real-time stock ticker where prices update every second. Each price is an observable; when the Websocket pushes a new price, the observable updates, and only the price cells on the page re-render — the rest of the dashboard stays untouched.
Observable Lifecycle
flowchart LR
A[ko.observable(initialValue)] --> B[Observable Function]
B -->|Read: observable()| C[Returns Stored Value]
B -->|Write: observable(newVal)| D[Notifies Subscribers]
D --> E[Update DOM]
D --> F[Update Computeds]
D --> G[Update Subscriptions]
E --> B
style B fill:#e6f3ff,stroke:#4a90d9,stroke-width:2px
Creating and Using Observables
An observable is created by calling ko.observable() with an optional initial value:
// Observable with initial value
var firstName = ko.observable('John');
var lastName = ko.observable('Doe');
var age = ko.observable(30);
// Observable without initial value (undefined)
var middleName = ko.observable();
// Reading values — always call as a function
console.log(firstName()); // Output: John
console.log(age()); // Output: 30
console.log(middleName());// Output: undefined
// Writing values — pass the new value as argument
firstName('Jane');
console.log(firstName()); // Output: Jane
Observable Notifications
When an observable's value changes, it notifies every subscriber synchronously. Subscribers include:
- DOM bindings that reference the observable
- Computed observables that depend on it
- Manual subscriptions created with
subscribe()
var counter = ko.observable(0);
// Subscribe to changes
counter.subscribe(function(newValue) {
console.log('Counter changed to: ' + newValue);
});
counter(1); // Console: Counter changed to: 1
counter(5); // Console: Counter changed to: 5
counter(5); // No notification (value did not change)
Expected output: The subscribe callback fires only when the value actually changes. Setting the same value again produces no notification.
Chaining Write Operations
Observables return the observable instance after a write, enabling method chaining:
var settings = ko.observable({
theme: 'dark',
fontSize: 14,
language: 'en'
});
// Chain multiple writes
settings(null)
.settings({ theme: 'light', fontSize: 16, language: 'fr' });
console.log(settings().language); // Output: fr
Using peek() to Read Without Dependency
Sometimes you need to read an observable's value inside a computed without creating a dependency. The peek() method reads the value without being tracked:
var clickCount = ko.observable(0);
var logMessage = ko.computed(function() {
// Read clickCount without creating a dependency
var count = clickCount.peek();
return 'Clicked ' + count + ' times. Logged at: ' + new Date().toLocaleTimeString();
});
clickCount(1); // logMessage updates because we wrote to clickCount
// But if clickCount is only read via peek(), the computed won't re-evaluate
Expected output: The computed observes the clickCount write (because a write always notifies), but subsequent reads inside the computed via peek() do not create re-evaluation dependencies.
Observable Arrays
ko.observableArray() tracks array mutation with special methods:
var items = ko.observableArray(['apple', 'banana', 'cherry']);
// Standard observableArray methods
items.push('date'); // Adds to end
items.pop(); // Removes from end
items.unshift('apricot'); // Adds to beginning
items.shift(); // Removes from beginning
items.splice(1, 1); // Removes one item at index 1
items.sort(); // Sorts in place
items.reverse(); // Reverses in place
// Replace entire array
items(['new', 'items']);
// Read current array
console.log(items()); // Output: ['new', 'items']
console.log(items().length); // Output: 2
Expected output: Using push, pop, and other array methods triggers notifications. Native array methods like items().push() do NOT notify — always use the observableArray methods.
Extending Observables with .extend()
Knockout provides built-in extenders that modify observable behavior:
var searchQuery = ko.observable('').extend({
rateLimit: { timeout: 300, method: 'notifyWhenChangesStop' },
required: true
});
var count = ko.observable(0).extend({
notify: 'always', // Always notify, even if same value
throttle: 100 // Limit update frequency
});
Common Mistakes
Forgetting parentheses when reading -
firstNamereturns the observable function;firstName()returns the value. Using the function where a value is expected can cause cryptic errors in bindings.Using native array methods instead of observableArray methods -
items().push(x)modifies the underlying array but does NOT notify subscribers. Always useitems.push(x).Mutating objects in place -
person.name('Bob')notifies subscribers;person().name = 'Bob'does not, because the observable still holds the same object reference.Creating observables inside a loop - Observables created inside a
forloop capture the loop variable by reference, not by value. Use a closure orletto capture correctly.Assuming equality check with objects - Observables use strict equality (
===) to detect changes. A new object{a:1}is always considered different from another{a:1}because they are different references.
Practice Questions
- How do you read the current value of an observable without creating a dependency?
- What happens when you assign the same value to an observable? Does it notify subscribers?
- Why should you prefer
observableArray.push()overobservableArray().push()? - What method do you use to limit how often an observable notifies subscribers?
- How does Knockout determine whether an observable's value has changed?
Challenge: Create a shopping cart observableArray of item objects (name, price, quantity). Implement functions to add, remove, and update items. Subscribe to the array to log every change to the console.
FAQ
Mini Project
Build a persistent settings panel where each setting (theme, fontSize, language) is an observable with rateLimit extenders. Save changes to localStorage on every update and load them on initialization.
What's Next
Observables track values, but what about derived values? Learn how computed observables automatically derive and cache values based on their dependencies.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro