Observable Arrays
If you want to detect and respond to changes on one object, you’d use observables. If you want to detect and respond to changes of a collection of things, use an observableArray
. This is useful in many scenarios where you’re displaying or editing multiple values and need repeated sections of UI to appear and disappear as items are added and removed.
Example
var myObservableArray = ko.observableArray(); // Initially an empty array
myObservableArray.push('Some value'); // Adds the value and notifies observers
To see how you can bind the observableArray
to a UI and let the user modify it, see the simple list example.
Key point: An observableArray tracks which objects are in the array, not the state of those objects
Simply putting an object into an observableArray
doesn’t make all of that object’s properties themselves observable. Of course, you can make those properties observable if you wish, but that’s an independent choice. An observableArray
just tracks which objects it holds, and notifies listeners when objects are added or removed.
Prepopulating an observableArray
If you want your observable array not to start empty, but to contain some initial items, pass those items as an array to the constructor. For example,
// This observable array initially contains three objects
var anotherObservableArray = ko.observableArray([
{ name: "Bungle", type: "Bear" },
{ name: "George", type: "Hippo" },
{ name: "Zippy", type: "Unknown" }
]);
Reading information from an observableArray
Behind the scenes, an observableArray
is actually an observable whose value is an array (plus, observableArray
adds some additional features described below). So, you can get the underlying JavaScript array by invoking the observableArray
as a function with no parameters, just like any other observable. Then you can read information from that underlying array. For example,
alert('The length of the array is ' + myObservableArray().length);
alert('The first element is ' + myObservableArray()[0]);
Technically you can use any of the native JavaScript array functions to operate on that underlying array, but normally there’s a better alternative. KO’s observableArray
has equivalent functions of its own, and they’re more useful because:
- They work on all targeted browsers. (For example, the native JavaScript
indexOf
function doesn’t work on IE 8 or earlier, but KO’sindexOf
works everywhere.) - For functions that modify the contents of the array, such as
push
andsplice
, KO’s methods automatically trigger the dependency tracking mechanism so that all registered listeners are notified of the change, and your UI is automatically updated which means there is a significant difference between using KO’s methods (i.e.,observableArray.push(...)
) and JavaScript native array methods (i.e.,observableArray().push(...)
) as the latter don’t send any notification to the array’s subscribers that its content has changed.
The rest of this page describes observableArray
’s functions for reading and writing array information.
indexOf
The indexOf
function returns the index of the first array item that equals your parameter. For example, myObservableArray.indexOf('Blah')
will return the zero-based index of the first array entry that equals Blah
, or the value -1
if no matching value was found.
slice
The slice
function is the observableArray
equivalent of the native JavaScript slice
function (i.e., it returns the entries of your array from a given start index up to a given end index). Calling myObservableArray.slice(...)
is equivalent to calling the same method on the underlying array (i.e., myObservableArray().slice(...)
).
Manipulating an observableArray
observableArray
exposes a familiar set of functions for modifying the contents of the array and notifying listeners.
pop, push, shift, unshift, reverse, sort, splice
All of these functions are equivalent to running the native JavaScript array functions on the underlying array, and then notifying listeners about the change:
push( value )
— Adds a new item to the end of array.pop()
— Removes the last value from the array and returns it.unshift( value )
— Inserts a new item at the beginning of the array.shift()
— Removes the first value from the array and returns it.reverse()
— Reverses the order of the array and returns theobservableArray
(not the underlying array).sort()
— Sorts the array contents and returns theobservableArray
. The default sort is alphabetical, but you can optionally pass a function to control how the array should be sorted. See the example undersorted
below.splice()
— Removes and returns a given number of elements starting from a given index. For example,myObservableArray.splice(1, 3)
removes three elements starting from index position 1 (i.e., the 2nd, 3rd, and 4th elements) and returns them as an array.
For more details about these observableArray
functions, see the equivalent documentation of the standard JavaScript array functions.
sorted and reversed
-
sorted()
— Returns a sorted copy of the array. This is preferable tosort
if you want to leave the observable array in its original order but need to display it in a specific order.The default sort is alphabetical, but you can optionally pass a function to control how the array should be sorted. Your function should accept any two objects from the array and return a negative value if the first argument is smaller, a positive value is the second is smaller, or zero to treat them as equal. For example, to sort an array of ‘person’ objects by last name, you could write:
var mySortedArray = ko.pureComputed(function () { return myObservableArray.sorted(function (left, right) { return left.lastName === right.lastName ? 0 : left.lastName < right.lastName ? -1 : 1; }); });
-
reversed()
— Returns a reversed copy of the array.
replace, remove and removeAll
observableArray
adds some more useful methods that aren’t found on JavaScript arrays by default:
replace( oldItem, newItem )
— Replaces the first value that equalsoldItem
withnewItem
.remove( someItem )
— Removes all values that equalsomeItem
and returns them as an array.remove( function (item) { return item.age < 18; } )
— Removes all values whoseage
property is less than 18, and returns them as an array.removeAll( ['Chad', 132, undefined] )
— Removes all values that equal'Chad'
,123
, orundefined
and returns them as an array.removeAll()
— Removes all values and returns them as an array.
destroy and destroyAll (Note: Usually relevant to Ruby on Rails developers only)
The destroy
and destroyAll
functions are mainly intended as a convenience for developers using Ruby on Rails:
destroy( someItem )
— Finds any objects in the array that equalsomeItem
and gives them a special property called_destroy
with valuetrue
.destroy( function (someItem) { return someItem.age < 18; } )
— Finds any objects in the array whoseage
property is less than 18, and gives those objects a special property called_destroy
with valuetrue
.destroyAll( ['Chad', 132, undefined] )
— Finds any objects in the array that equal'Chad'
,123
, orundefined
and gives them a special property called_destroy
with valuetrue
.destroyAll()
— Gives a special property called_destroy
with valuetrue
to all objects in the array.
So, what’s this _destroy
thing all about? It’s only really interesting to Rails developers. The convention in Rails is that, when you pass into an action a JSON object graph, the framework can automatically convert it to an ActiveRecord object graph and then save it to your database. It knows which of the objects are already in your database, and issues the correct INSERT or UPDATE statements. To tell the framework to DELETE a record, you just mark it with _destroy
set to true
.
When Knockout renders a foreach
binding with the parameter includeDestroyed: false
set, it will hide any objects marked with _destroy
equal to true
. So, you can have some kind of “delete” button that invokes the destroy(someItem)
method on the array, and this will immediately cause the specified item to vanish from the visible UI. Later, when you submit the JSON object graph to Rails, that item will also be deleted from the database (while the other array items will be inserted or updated as usual).
Determining if a property is an observableArray
In some scenarios, it is useful to programmatically determine if you are dealing with an observableArray. Knockout provides a utility function, ko.isObservableArray
to help with this situation.
Delaying and/or suppressing change notifications
Normally, an observableArray
notifies its subscribers immediately, as soon as it’s changed. But if an observableArray
is changed repeatedly or triggers expensive updates, you may get better performance by limiting or delaying change notifications. This is accomplished using the rateLimit
extender like this:
// Ensure it notifies about changes no more than once per 50-millisecond period
myViewModel.myObservableArray.extend({ rateLimit: 50 });
Tracking array changes
Although you can subscribe to and access an observableArray
just like any other observable, Knockout also provides a super-fast method to find out how an observable array has changed (i.e., which items were just added, deleted, or moved). You subscribe to array changes as follows:
obsArray.subscribe(fn, thisArg, "arrayChange");
The main advantages of subscribing to changes:
-
Performance is
O(1)
in most cases, i.e., there’s basically no performance implication at all, because for straightforward operations, (push
,splice
, etc.) Knockout supplies the change log without running any difference algorithm. Knockout only falls back on an algorithm if you’ve made an arbitrary change without using a typical array mutation function. -
The change log just gives you the items that actually changed.
Here are examples of how the changes are reported:
var myArray = ko.observableArray(["Alpha", "Beta", "Gamma"]);
myArray.push("Delta");
// Changes: [{ index: 3, status: 'added', value: 'Delta' }]
// New value: ["Alpha", "Beta", "Gamma", "Delta"]
myArray.pop();
// Changes: [{ index: 3, status: 'deleted', value: 'Delta' }]
// New value: ["Alpha", "Beta", "Gamma"]
myArray.splice(1, 2, "Omega");
// Changes:
// [{ index: 1, status: 'deleted', value: 'Beta' },
// { index: 1, status: 'added', value: 'Omega' },
// { index: 2, status: 'deleted', value: 'Gamma' }]
// New value: ["Alpha", "Omega"]
myArray.reverse();
// Changes:
// [{ index: 0, moved: 1, status: 'deleted', value: 'Alpha' },
// { index: 1, moved: 0, status: 'added', value: 'Alpha' }]
// New value: ["Omega", "Alpha"]
As shown above, the changes are reported as a list of added and deleted values. The indexes for deleted items refer to the original array, and the indexes for added items refer to the new array.
When items are re-ordered, as shown in the last example above, you will also get moved information. You can choose to ignore the moved information and just interpret it as the original Alpha
being deleted and a different Alpha
being added to the array’s end. Or you can recognize that the moved information tells you that you can think of the added and deleted values being the same item that just changes position (by matching up the indexes).
An observableArray
has array tracking enabled at construction, but you can extend any other subscribable
(i.e. ko.observable
and ko.computed
) as follows:
trackable = ko.observable().extend({trackArrayChanges: true});