-2

I have an ko.observable with an object that contains 3 arrays like this:

self.filter({ file: [], site: [], statut: [] })`

When I try to empty them it doesn't work. I tried

array = []

to empty them. Is it a problem with the observable?

Jeroen
  • 60,696
  • 40
  • 206
  • 339
Yepzy
  • 25
  • 1
  • 6

2 Answers2

2

You don't need all of your observable object's arrays to be observable to be able to update the UI, although I'd certainly (like the other answerer) advice you to do so.

I would like to explain however why it doesn't work.

Say you have the following code:

var originalObject = {
  myArray: [1, 2, 3]
};

var myObservable = ko.observable(originalObject);

// Resetting the array behind knockout's back:
originalObject.myArray = [1, 2, 3, 4];

The last line changes a property of the object that was used to set the observable. There's no way for knockout to know you've updated your object. If you want knockout to reconsider the observable's vale, you have to tell it something's changed:

myObservable.valueHasMutated();

Now, normally, you update an observable by passing a new or updated variable to it like so:

myObservable(newValue);

Strangely, setting the observable with the same object again also works:

myObservable(originalObject);

This is why:

Internally, knockout compares the newValue to the value it currently holds. If the values are the same, it doesn't do anything. If they're different, it sets the new value and performs the necessary UI updates.

Now, if you're working with just a boolean or number, you'll notice knockout has no problems figuring out if the new value is actually different:

var simpleObservable = ko.observable(true);

simpleObservable.subscribe(function(newValue) {
  console.log("Observable changed to: " + newValue);
});

simpleObservable(true);   // Doesn't log
simpleObservable(false);  // Does log
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>

For objects however, it 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);   // Does log, although nothing changed
simpleObservable({b: 2 });     // Does log
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>

The subscription is triggered, even though we've used the exact same object to reset our observable! If you dig through knockout's source code, you'll see why. It uses this method to check if the new value is different:

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;
}

Simply put: if the old value isn't a primitive value, it will just assume things have changed. This means we can update our originalObject, as long as we reset the observable.

originalObject.myArray.length = 0;
myObservable(originalObject);

Or, just as easy:

myObservable(Object.assign(originalObject, { myArray: [] });

A bit of a long answer, but I believe it's nice to know why stuff doesn't work, instead of only circumventing it. Even if simply using observableArrays and letting knockout optimize its work is the better solution!

user3297291
  • 22,592
  • 4
  • 29
  • 45
1

You mention "emptying" an array. Note that that's different from "assigning a new, empty array to a variable". At any rate, if you want to "empty" an array:

  1. For observableArray check the relevant docs, because they have a removeAll() utility method.
  2. For emptying a plain javascript array, check this duplicate question that has various solutions, one of which is simply array.length = 0.

As a final note, if you're inside the view model, you might need to do self.filter() first to get the object inside the observable. So, for example:

self.filter().file.length = 0; // plain array method

However, since file, site, and statut are plain arrays (and not observableArrays) there will be no automatic updates in your UI. If they were observable arrays, you'd do:

self.filter().file.removeAll(); // assuming `file` has been made observable
Community
  • 1
  • 1
Jeroen
  • 60,696
  • 40
  • 206
  • 339