Updating the UI doesn't require all arrays in your observable object to be observable. However, it is recommended for better practice.
Let me explain why this approach may not work:
Imagine having the following code snippet:
var originalObject = {
myArray: [1, 2, 3]
};
var myObservable = ko.observable(originalObject);
// Modifying the array without informing knockout:
originalObject.myArray = [1, 2, 3, 4];
The last line alters a property of the object used to set the observable. Knockout is unaware of this change unless explicitly told so. To prompt knockout to reevaluate the observable value, you must signal that a mutation has occurred:
myObservable.valueHasMutated();
Typically, updating an observable involves assigning a new or modified value directly:
myObservable(newValue);
Oddly, setting the observable again with the same object also functions correctly:
myObservable(originalObject);
Here's why:
Internally, knockout compares the `newValue` against its current value. If they match, no action is taken. If they differ, the new value is set, triggering the necessary UI updates.
Notably, when dealing with primitive types like `boolean` or `number`, knockout effortlessly detects discrepancies in values:
var simpleObservable = ko.observable(true);
simpleObservable.subscribe(function(newValue) {
console.log("Observable changed to: " + newValue);
});
simpleObservable(true); // No output
simpleObservable(false); // Outputs change message
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
However, for objects, knockout behaves differently:
var myObject = { a: 1 };
var simpleObservable = ko.observable(myObject);
simpleObservable.subscribe(function(newValue) {
console.log("Observable changed to: " + JSON.stringify(newValue, null, 2));
});
simpleObservable(myObject); // Triggers event despite no changes
simpleObservable({b: 2 }); // Event gets triggered
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
Even if we reset the observable using the exact same object, the subscription still activates! This unexpected behavior stems from how knockout compares values:
var primitiveTypes = { 'undefined':1, 'boolean':1, 'number':1, 'string':1 };
function valuesArePrimitiveAndEqual(a, b) {
var oldValueIsPrimitive = (a === null) || (typeof(a) in primitiveTypes);
return oldValueIsPrimitive ? (a === b) : false;
}
In essence, non-primitive values are always considered different by knockout. Therefore, we can modify our `originalObject` as long as we update the observable accordingly:
originalObject.myArray.length = 0;
myObservable(originalObject);
Alternatively, use Object.assign for simplicity:
myObservable(Object.assign(originalObject, { myArray: [] }));
While this explanation may seem lengthy, understanding the underlying mechanisms behind such issues is crucial. It's beneficial to comprehend why certain approaches fail rather than just finding ways around them. Even so, utilizing observableArrays and allowing knockout to optimize its operations remains the ideal solution!