Collection pipelines with Vue.js

November 27, 2016, 9:30 pm

Off late I've been finding Vue.js computed properties can be used in some very interesting and powerful ways.

Let's build a table with Vue.js which supports:

  • Sorting
  • Searching
  • Pagination

We start off with a template like this:

<table>
  <thead>
    <tr>
      <th @click="sortBy('name')">Name</th>
      <th @click="sortBy('name')">Age</th>
  </thead>
  <tbody>
    <tr v-for="row in table_data">
      <td>{{ row.name }}</td>
      <td>{{ row.age }}</td>
    </tr>
</table>
<input v-model="search_text" placeholder="Search" />
<a @click="prevPage">Prev</a>
<a @click="nextPage">Next</a>

And a component with data similar to this:

data () {
  return {
    table_data: [
      {name: 'John', 'age': 24},
      // ... lots of rows
    ],
    // text to search on
    search_text: "",

    // column to sort by
    sort_column: 'name',

    // current page number
    page_num: 0
  }
}

We're going to take a bunch of steps to turn this data from "raw" data into "presentable" data.

There are different ways to do this. We'll be accomplishing this with vue's computed properties. Let's declare a property called final_data which does filtering, sorting and paginating.

    <tr v-for="row in final_data">
      <td>{{ row.name }}</td>
      <td>{{ row.age }}
    </tr>
computed: {
  final_data () {
    var rows = this.table_data;

    // Let's assume some function exists to filter rows
    // this
    rows = filterRows (rows, this.search_text);

    // Let's assume some function exists to sort rows
    rows = sortRows (rows, this.sort_column);

    // Let's assume some function which paginates the data
    rows = paginateData (rows, this.page_num)

    return rows;
  }
}

This is neat. When the variables like search_text, page_num change, then final_data is automatically recalculated.

However this is quite inefficient. Let's say only page_num changes. The results of sorting and filtering will remain the same. Still, filterRows and sortRows are called each time.

This is looking like pattern called the collection pipeline. A lot of languages (mostly functional) have a feature to build "pipelines" where data flows from one point to the other.

Let's re-structure the computed final_data property and add two more computed properties:

computed: {
  final_data () {
    return paginateRows (this.sorted_data, this.page_num);
  },
  sorted_data () {
    return sortRows (this.filtered_data, this.sort_column);
  },
  filtered_data () {
    return filterRows (this.table_data, this.search_text);
  }
}

Let's say the page number changes with page_num. Vue.js detects that final_data needs to be re-calculated. But this uses an existing value of this.sorted_data because Vue.js determined the property hasn't changed.

Now, if we change a page:

If we change the sort column:

If we change the search text:

The really amazing thing is that Vue.js is keeping track of dependencies for us. We ended up getting a full dependency tree and lazy evaluation while making the code simpler.

Hope this pattern can help you clean up and speed up your apps.

Next Post Previous Post