Wednesday, March 23, 2016

Filtering Table Data with Knockout Computeds

Before learning to love Knockout, I was an AngularJS fan. AngularJS has this really cool feature called filters that let you pipe a collection through a filter, filtering results based on the value of a field. The AngularJS filter page has a great inline example. You can filter with Knockout, but it isn't quite as simple. The key to filtering in Knockout is the computed observable. I put together an example:

This example contains a search field in the upper right corner as well as a table of employee names. If the filter field is empty, then the table should display all employees. If the filter field contains a value, then the table should display only employees with names containing the search value. When you look at the JavaScript for this example, you will see:

  1. An array of employees (the raw, unobserved data),
  2. An observable for the search value, and
  3. A computed for the filtered table

Drilling into the computed observable (filteredEmployees), we see that the function immediately returns the list of all employees if the nameSearch observable has no value. If it has a value, then it returns a filtered array of matching results.

I put together 2 examples: One with ES6 Array and String extensions and a Lazy.js version. The example above is the Lazy.js version. ES6 is great, but jsPerf tests show better results for ko.utils, underscore, Lazy.js, or just about any other non-native library. I also hesitate to use the ES6 Array.prototype.filter for browser compatibility reasons. Just in case you are interested, Here is the ES6 version. Why Lazy.js instead of just ko.utils.arrayFilter? I am a big fan of Lazy.js's function composition rather than the traditional chained intermediate array concept (even though this example doesn't exactly chain enough array methods together to see a performance improvement from Lazy.js).

On the HTML View side, the search field's data-bind attribute uses the valueUpdate parameter. This causes Knockout to update the ViewModel on some other event besides the change event. That way users can see changes as they type. What this means is as you type, the filter code will run, filtering the results displayed in the table. We have a small data set, so you won't notice, but on a larger data set, this could have serious performance implications because each key press would iterate over the Employees array. We can limit how often knockout recomputes the computed observable by debouncing, or rate limiting, updates of nameSearch field like this:

self.nameSearch.extend({
    rateLimit: {
      timeout: 500,
      method: "notifyWhenChangesStop"
    }
  });

You can see an example here. Notice that the update is a little choppier, meaning the table filters a half second after you stop typing. A half second may be a little too long between updates. The important part is that the code recalculates the computed AFTER the specified event pauses for a predetermined interval.

No comments:

Post a Comment