Search Filters & Debouncing in Ember: No Addons Necessary

There are lots of amazing articles and resources that I found in helping people get started with a search filter in Ember. There are plenty of addons and they are pretty good and useful. Having worked with Rails, I remember one major critique being how people bloated up their apps with gems. The same can be said about any language- introducing any external modules of code has its trade-offs.

I feel search filters are pretty ubiquitous to web experiences now, and I should not have to resort to an expensive proposition to get it to work. Unless I really have to.

I also wanted to see Ember's approach to implement this feature. It's actually not that bad. Not a lot of code, but definitely some concepts to keep in mind.

Filtering

Scope: All the work can be accomplished within 2 files: the template and the controller. Didn't even need to generate a component.

Note:

In this particular example, all the data has already been loaded in the beginning. So the filtering will just match the results that are already there. Some filters make a new AJAX call for every search request, we're not going to be doing that for this post.

If that's the type of filtering you want to do, then check out some of the resources I've listed at the end of this post. Hint: Ember Concurrency. I tried it on another project for exactly the same purpose, and it was pretty neat.

Setup

Let's start the template with a simple input form, and an iteration block which displays all the data from the model.

app/templates/index.hbs:

{{input value=search}}

`


    {{#each model.data as |config|}}
  • {{config.description}}

  • {{/each}}

`

app/controllers/index.js

import Ember from 'ember';

export default Ember.Controller.extend({ queryParams: ['search'], search: '' });

Here we are setting a search value in the input field, and the controller will set the url's query param as this search value. So you will essentially be filtering out the results which match the string/number you enter into the input field. And the controller will be displaying the associated data tied to the model. If you try it out, you can actually see the url change as you type into the input field.

And if you noticed, the page doesn't change or get updated. That's because we haven't set up a filtered property yet.

Let's update our Controller, with a new Ember computed property called 'filtered':

Here a couple of concepts that are being introduced, which are definitely worth reading about:

If you are familiar with these concepts or just read up on them right now, the above code should be pretty self explanatory.

You are introducing a new property filtered in your controller, which is really a function, but can be used as a static property. We use this to manipulate the dynamic search value and model.data we have at hand, and create dynamically filtered results.

Now make one small change in your index.hbs, instead of iterating over your model.data, now iterate over the computed property 'filtered'. And voila. You can now dynamically filter your data!

Debouncing

The great part of the above code is that Ember makes it works. The transition isn't clunky (if you use something like refreshModel in your model, to fetch data on every request then it would be). Debouncing is a good feature to add irrespectively, and a precautionary measure against really fast typers who want to be dicks and break our product and be smug about it. Hate those guys.

Debouncing add-on:

  • The ember-debounced-input-helpers came recommended by a colleague, and I saw how the code was used. Pretty simple and minimal and you should definitely check it out.

In case you wanted to write your own debounce function, keep reading.

First read about debouncing first from the Ember Guides:

Let's put it all together now. Before your filtered property in your controller add the following code:

app/controller/index.js

filter: Ember.computed.oneWay('search'),

onFilter: Ember.observer('filter', function() { Ember.run.debounce(this, this.triggerFilter, 100); }),

triggerFilter: function() { this.set('search', this.get('filter')); },

Also in your index.hbs now change the value=search, to value=filter in your input field.

Voila: You can debounce that filter.

... So we are functionally setting the search value into the filter property one-way. You have an onFilter property whose job is to observe any changes to filter. This also runs the debounce function which will trigger the triggerFilter function after 100ms of the filter not being changes.

Resources I found useful: