7

I have an array of objects that are hooked up into a knockout observable array. I needed to apply sorting to these arrays and I ran into some behavior that is a bit confusing.

My first attempt involved applying the sort in the foreach data-binding.
http://jsfiddle.net/wnfXV/

<ul data-bind="foreach: people.sort(function(l,r) { return l.name == r.name ? 0 : (l.name < r.name ? -1 : 1)})">

This performs the proper sorting, but I lose the ability to dynamically add/remove elements from the array and have the DOM update.

If I add a set of parenthesis to access the underlying JavaScript array, everything works fine.

<ul data-bind="foreach: people().sort(function(l,r) { return l.name == r.name ? 0 : (l.name < r.name ? -1 : 1)})">

Based on some SO answers I found, I ended up creating a computed observable for the sorted array. http://jsfiddle.net/wnfXV/2/

self.sortedPeople = ko.computed(function() {
    return self.people().sort(function(l,r) {
        return l.name == r.name ? 0 : (l.name < r.name ? -1 : 1);
    });
});

This also works. And I don't even need to data-bind to the computed observable since it is executed immediately. I can push and remove array items and the DOM updates appropriately.

However, if I change the code to:

self.sortedPeople = ko.computed(function() {
    return self.people.sort(function(l,r) {
        return l.name == r.name ? 0 : (l.name < r.name ? -1 : 1);
    });
});

Now, I am able to push items to the array and the DOM updates, but the data is not being sorted.

I think these differences are related to knockout dependency tracking, and the difference between operating on an observable array and the native JavaScript array underneath it, but I'm having a hard time conceptualizing why the behavior is changing. I am able to get it to work, but I'm also curious as to what is considered best practice.

Thanks for any help. :)

ecozoic
  • 113
  • 1
  • 1
  • 6

2 Answers2

2

Problem with using sort in views is actually not recommended by KO because with this approach observableArray.sort doesn't establish a dependency on the array, so the binding won't get updated.

So if you want to make it work one can use

items.slice(0).sort()

for more detailed look at
https://github.com/knockout/knockout/issues/1380

Fiddle:http://jsfiddle.net/mbest/6dmAn/

Anshul Nigam
  • 1,608
  • 1
  • 12
  • 26
1

In your JS Fiddle it's because your foreach is bound to people... not to sortedPeople.

The reason its sorted the first time is because the computed runs once... but doesn't subscribe.

However, the computed ends up running again when you use the parenthesis because of some subscription on the underlying array.

Edit:

When you use the parenthesis, the computed is subscribing to the array when the observable is invoked. When the subscribed item changes, the compute is re-executed.

LameCoder
  • 1,287
  • 7
  • 22
  • It might be more clear if you just subscribed to the observable array instead of using the computed. You're basically using the computed for its subscription. http://jsfiddle.net/N9sGY/ – LameCoder Jan 21 '14 at 20:43
  • yeah, that explains the last example where items are added but not resorted. thanks. any ideas why the push/remove functionality doesn't work in the first fiddle, or why adding the parenthesis fixes it? – ecozoic Jan 21 '14 at 21:13
  • In the first fiddle it is basically the same thing. Since you aren't using the parenthesis, there isn't a subscription being made. Doing people.sort is basically short-circuiting the mechanism that subscribes to changes. – LameCoder Jan 21 '14 at 21:19