Hey, everyone. Today, I want to take a look at building a search directive that's going to query a back end and update a list that we have on our scope. To show you what I mean here, we've got a list of contacts.
We're only showing five of the contacts right now. Notice we only have one David in the list here. If I begin typing in here, we will query the back end and find David Black and, in fact, Ryan Davis as well.
Some people have asked me if you can do this using a filter. The answer is you can do this using a filter, but I would highly advise you not to do this using a filter. I'm going to show you why here.
This may look like we're using a filter for this. We are using a filter, but we're not using a filter in the way that you might be thinking. The way a filter would work is that it's going to run on every keystroke.
I'm going to simulate exactly what that's going to look like. Here, I actually have this functionality running on every keystroke, using jQuery. This is exactly the same thing that would happen if you were using a filter.
Now, if we reload this here and I type in "David," you see that's going to be a lot more chunky in our animation. It's going to actually run five times, once for every keystroke.
The way that we can resolve that is by adding a timeout. What happens when we run each keystroke when this function runs is we create a timeout, and that timeout's going to run after 200 milliseconds.
It's actually going to run when two conditions are true, when 200 milliseconds have passed and when the thread is free, which may not be exactly concurrent. They usually synch up fairly well.
When the thread is free and after 200 milliseconds has passed, that's when we're going to actually build the query and make the query on the back end. If that doesn't happen, if we get another keystroke before that point in time, we're actually going to cancel the timeout that exists already, that's assigned here to search timeout.
You see we have this in a higher-level scope so that it will be available to each of these key-ups. If that exists, we're just going to cancel it, and we're going to set a new one.
Now, if we re-save this here, what actually ends up happening is if I type pretty quickly, as people do, it's not going to fire each of my back-end requests, which are fairly long-running requests.
We get a smoother animation here. Instead of seeing that occur five times, it probably only happens once, depending on how quickly I'm typing. If I also pause while I'm typing, we want that to take a second and then offer a suggestion from our back end.
That works the way that we'd expect it to, now. That's why we don't want to use a filter to build this. That's why we want to use a directive to build this.
I want to show you some other options that I like to expose on filtering and searching functionality. The first thing that I like to expose here is the actual search value. If I haven't set any value, what I do here is I set this equal to any.
I set query.any equals the value that's passed in. That might be "query.any = David," in the case that I've been showing you so far. Otherwise, it's going to say, perhaps, "query.firstname = David," and that will get assigned in and get passed to the back end.
This is just something that I've set up my API to work with. Here, I'm going to set this to first name. Remember, we were getting Davis from the back end, which was a last name. Now, we're only going to get first names that start with "Dav."
That's an option that I like to expose, that's going to allow this directive to have a little bit more fine-grained functionality into how it controls this search. I'm going to delete that right now, but you know exactly what that does at this point.
The other things that I like to expose to make this reusable are the model that we're going to use. Here, I'm using the contact model. I'm actually exposing that on my controller scope so that we have access to it here.
This is probably what you'd expect to see, scope.contact equals the contact model. The reason I'm actually going and doing that is because if we use a $resource model, any one of those models will have a query method.
I actually just call down onto whatever that model is and call its query method. Then, what I'm going to do is actually take whatever is passed into scope.update and use that to update the list here.
That way, I can put any generic list in here. Here, I have scope.contacts as the list to update. If I had multiple lists on my scope, I could update any one of them here.
Another option that I like to pass in from my API...Again, this is just a little bit of functionality that I think is nice, from the API...is fuzzy equals true. If I set this to false or I fail to set it, then my directive's actually going to set it to false for me.
Now, if I type in "dav," I'm going to lose every one of these. I actually have to type in capital "D," "David," or else it won't be an exact match. What that's actually running on my back end, if I have fuzzy set to true, if I hop into my controllers here...
You don't have to know too much about Rails code. There's just a little SQL in here. It's actually going to chain together any of my queryable keys. I have first name, last name, and email as queryable.
It will say, "First name I like," which is case-insensitive. Lower-case "da" would match "David" in the first name column and also in the last name column, in "Davis." It'll build up a query here, for me. Just to take a little look under the hood as to how we expose that on the back end.
Those are the options that I like to expose to this search directive. Finally, I want to show you why we're actually using this filter, down here. This is a very basic filter. It's actually using this ng-model for the search.
This is going to filter the results that we have in place. If we actually take this off, we'll see that our other event really does only fire after 200 milliseconds have passed. Only once the user has finished typing something in does this fire to the back end, and then we filter down our results.
If, on the other hand, we go ahead and use this filter, refresh the page, and type in "David," we see that we start filtering down our result set immediately. After this timeout has fired, then we load in David Black, and we can add him after an appropriate amount of time has passed.
It feels really fluid. It feels very responsive because we get this response almost immediately, from Angular. That's a nice thing about these built-in filters, they allow us to complement a request like this search directive with live, in-place filtering.
There are lots of good ways to do this, and I'm sure there are lots of good questions you have about how to do this. The rest of the information that you'll need is in the show notes, such as this code. If you have additional questions, please leave them in the comments, and I'm happy to answer them for you. Take it easy, everyone.