Knockout Observable Array with Sorting and Foreach Data-Binding
Asked Answered
F

2

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. :)

Fireboard answered 21/1, 2014 at 20:8 Comment(0)
P
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.

Phina answered 21/1, 2014 at 20:35 Comment(3)
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. jsfiddle.net/N9sGYPhina
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?Fireboard
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.Phina
F
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/

Freelance answered 2/2, 2015 at 9:55 Comment(0)
P
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.

Phina answered 21/1, 2014 at 20:35 Comment(3)
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. jsfiddle.net/N9sGYPhina
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?Fireboard
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.Phina

© 2022 - 2024 — McMap. All rights reserved.