Type-Ahead Filtering in AngularJS

So, I ran into an interesting little challenge a couple of days ago. I was experimenting with some AngularJS code that would eventually be tasked with using SharePoint JSOM REST API’s to interact with various document libraries hosted in both SharePoint and OneDrive. I was chiefly interested in implementing a client-side, “type-ahead” filtering solution, meaning one that filters on the entire array of records that are returned from the SharePoint server.

Unfortunately, due to a staggering number of records being returned from SharePoint, some serious performance degradation issues arose. In several cases, it was taking the server well over a minute to return 10,000 rows to us. To address the lag time, we pared down the data to only include the attributes of interest to us. This helped, but the server was still far too slow.

After brainstorming with the rest of the team, we decided that server-side filtering was worth a shot. Bear in mind that “type-ahead” filtering can be extraordinarily slow on the server, particularly if the GET calls are summonsed on a “keystroke-by-keystroke” basis. For example, if your very first keystroke happens to be the letter ‘s’, then you should expect to return every file name with the letter ‘s’ in it, and with that goes a sizable chunk of your time and the server’s resources.

Likewise, if the next letter you type happens to be an ‘h’, the combination of the ‘s’ and the ‘h’ will cause the filter to run a little bit quicker than just a single character. But, the query would still take considerable time to complete, and the results will still be overwhelming and therefore useless to you.

Obviously, adding additional characters to your search string will continue to pare down the results and improve the server’s response time. But, when it all boils down to it, you don’t really care about returning results for things like ‘s’, ‘sh’, or ‘sha’. You actually only care about returning the results for the word ‘shazam’.

THE PROPOSED APPROACH

With this in mind, our proposed approach is to streamline the query attempts against the SharePoint server by only submitting SharePoint GET requests when user has gone without modifying their search text for an arbitrary amount of time. I’ll use 1500 milliseconds, or 1.5 seconds, in my upcoming code example.

One underlying benefit of this approach is to provide a user ample time to type in a search term, as well as give them time to make modifications to it and correct any misspellings, before initiating a GET call to the SharePoint server’s REST API stack. Another benefit is that it presents the user with an illusion that “type-ahead” filtering is actually taking place, when in fact it isn’t. The impact of this approach leads us to our overall objective, which is minimizing the number of calls to the SharePoint server and yielding more pertinent and smaller result sets back to the client application.

APPROACH CAVEATS

Unfortunately, building a sophisticated “type-ahead” solution in JavaScript isn’t always as easy as it sounds. Depending upon the functional and non-functional requirements, it can quickly become very challenging. In our case, we’ll need to keep track of the “onkeypress” or the “onkeyup” events that are tasked with alerting JavaScript functions to take some action on the SharePoint REST API’s.

Next, we’ll need a mechanism that evaluates whether or not there has been 1.5 seconds of inactivity on the model before executing the target function, but this only applies if there have been changes to the model. What’s more, if user modifies the model within the 1.5 second threshold, then the expiration slides another 1.5 seconds before the next SharePoint REST call can be made. But, none of this should occur if the model is either null or empty. As you can see, the more I discuss it, the more complex the functionality becomes.

THE SOLUTION

Alas, bring on AngularJS! It provides us with a very simple OOB solution that tackles this exact problem. In fact, everything I just mentioned can be brought to fruition by combining the following few lines of HTML, AngularJS, and JavaScript code:

Wow! Do you think you can handle all of that? Seriously though, that’s the solution in a nutshell. Everything I just talked about is now in play using the skillfully applied and illusory techniques I shared with you in the code example above. What’s more, I can easily explain the functionality to you in two simple bullet points:

Debounce: 1500 – Part of the AngularJS ngModelOptions, this directive tells AngularJS to update the model value using the number of milliseconds provided. In this case, I’ve specified 1500 milliseconds, or 1.5 seconds.

$scope.$watch(‘delayedSearchFilter’, function(nVal, oVal) – This method signature subscribes to the ‘delayedSearchFilter’ model and gets invoked every 1500 milliseconds (or whatever the debounce duration is set to) if there are changes to the delayedSearchFilter model. I should point out that this function doesn’t fire when the model is empty or if the model hasn’t changed since the last time the function was invoked. How incredibly convenient is that? It’s like they read my mind!

From my perspective, the team at Google has probably spent a staggering amount of time and money on both maintaining and perfecting the AngularJS Framework. Additionally, I surmise that a large number of developers, many who are far more talented and smarter than I am, have contributed their IT prowess to the AngularJS Framework. These things being the case, why in the world would I ever want to write my own functionality to tackle this problem when I can simply leverage their proven work?

In the end, my custom JavaScript code might take some time to write, it might be error-prone, and it might require additional changes over multiple test cycles in order to make it production worthy. But, by leveraging the AngularJS Framework’s OOB built-in functionality the way it’s intended to be leveraged, I’m able to markedly decrease my development time while simultaneously increasing the probability of writing bullet-proof code on my very first attempt, which makes this a win-win solution for everyone!