Creating a Typeahead Widget with AngularJS

By Sandeep Panda

If you are starting an AngularJS project you might want to have all the components written in Angular. Although it’s certainly possible to reuse the existing jQuery plugins, throwing a bunch of jQuery inside a directive is not always the correct way to do things. My advice would be to first check if the same thing can be implemented with pure Angular in a simpler/better way. This keeps your application code clean and maintainable. This tutorial, targeted towards beginners, walks the readers through the creation of a simple TypeAhead widget with AngularJS.


In this tutorial we are going to build a simple TypeAhead widget which creates suggestions as soon as someone begins typing into a text box. We will architect the app in such a way that the final product will be very configurable and can be plugged into an existing system easily. The basic steps involved in the creation process are:

  • Create a factory that interacts with a RESTful API, and returns JSON that will be used for auto complete suggestions.
  • Create a directive that will use the JSON data and encapsulate the typeahead input field.
  • Keep the directive configurable so that end users can configure the following options.

Configuration Options

  1. The exact JSON object properties to show as part of the suggestions.
  2. The model in the controller’s scope that will hold the selected item.
  3. A function in the controller’s scope that executes when an item is selected.
  4. A placeholder text (prompt) for the typeahead input field.

Step 1: Building a Factory for Getting Data

As the first step, let’s create a factory that uses Angular’s $http service to interact with RESTful APIs. Have a look at the following snippet:

var typeAhead = angular.module('app', []);

typeAhead.factory('dataFactory', function($http) {
  return {
    get: function(url) {
      return $http.get(url).then(function(resp) {
        return; // success callback returns this

The previous code creates a factory called dataFactory that retrieves JSON data from an API. We won’t go into the details of the factory, but we need to briefly understand how the $http service works. You pass a URL to the get() function, which returns a promise. Another call to then() on this promise also returns another promise (we return this promise from the factory’s get() function). This promise is resolved with the return value of the success callback passed to then(). So, inside our controller, we don’t directly interact with $http. Instead, we ask for an instance of factory in the controller and call its get() function with a URL. So, our controller code that interacts with the factory looks like this:

typeAhead.controller('TypeAheadController', function($scope, dataFactory) { // DI in action
  dataFactory.get('states.json').then(function(data) {
    $scope.items = data;
  $ = ''; // This will hold the selected item
  $scope.onItemSelected = function() { // this gets executed when an item is selected
    console.log('selected=' + $;

The previous code uses an API endpoint called states.json that returns a JSON list of US States. When the data is available, we store the list in the scope model items. We also use the model name to hold the selected item. Finally, the function onItemSelected() gets executed when the user selects a particular state.

Step 2: Creating the Directive

Let’s start with the typeahead directive, shown below.

typeAhead.directive('typeahead', function($timeout) {
  return {
    restrict: 'AEC',
    scope: {
      items: '=',
      prompt: '@',
      title: '@',
      subtitle: '@',
      model: '=',
      onSelect: '&'
    link: function(scope, elem, attrs) {
    templateUrl: 'templates/templateurl.html'

In the directive we are creating an isolated scope that defines several properties:

  • items: Used to pass the JSON list to the isolated scope.
  • prompt: One way binding for passing placeholder text for the typeahead input field.
  • title and subtitle: Each entry of the auto complete field has a title and subtitle. Most of the typeAhead widgets work this way. They usually (if not always) have two fields for each entry in the drop down suggestions. If a JSON object has additional properties, this acts as a way of passing the two properties that will be displayed in each suggestion in the dropdown. In our case the title corresponds to the name of the state, while subtitle represents its abbreviation.
  • model: Two way binding to store the selection.
  • onSelect: Method binding, used to execute the function in the controller scope once the selection is over.

Note: An example JSON response is shown below:

  "name": "Alabama",
  "abbreviation": "AL"

Step 3: Create the Template

Now, let’s create a template that will be used by the directive.

<input type="text" ng-model="model" placeholder="{{prompt}}" ng-keydown="selected=false" />

<div class="items" ng-hide="!model.length || selected">
  <div class="item" ng-repeat="item in items | filter:model  track by $index" ng-click="handleSelection(item[title])" style="cursor:pointer" ng-class="{active:isCurrent($index)}" ng-mouseenter="setCurrent($index)">
    <p class="title">{{item[title]}}</p>
    <p class="subtitle">{{item[subtitle]}}</p>

First, we render an input text field where the user will type. The scope property prompt is assigned to the placeholder attribute. Next, we loop through the list of states and display the name and abbreviation properties. These property names are configured via the title and subtitle scope properties. The directives ng-mouseenter and ng-class are used to highlight the entry when a user hovers with the mouse. Next, we use filter:model, which filters the list by the text entered into the input field. Finally, we used the ng-hide directive to hide the list when either the input text field is empty or the user has selected an item. The selected property is set to true inside the handleSelection() function, and set to false false (to show the suggestions list) when someone starts typing into the input field.

Step 4: Update the link Function

Next, let’s update the link function of our directive as shown below.

link: function(scope, elem, attrs) {
  scope.handleSelection = function(selectedItem) {
    scope.model = selectedItem;
    scope.current = 0;
    scope.selected = true;
    $timeout(function() {
    }, 200);
  scope.current = 0;
  scope.selected = true; // hides the list initially
  scope.isCurrent = function(index) {
    return scope.current == index;
  scope.setCurrent = function(index) {
    scope.current = index;

The function handleSelection() updates the scope property, model, with the selected state name. Then, we reset the current and selected properties. Next, we call the function onSelect(). A delay is added because the assignment scope.model=selecteditem does not update the bound controller scope property immediately. It is desirable to execute the controller scope callback function after the model has been updated with the selected item. That’s the reason we have used a $timeout service.

Furthermore, the functions isCurrent() and setCurrent() are used together in the template to highlight entries in the auto complete suggestion. The following CSS is also used to complete the highlight process.

.active {
  background-color: #C44741;
  color: #f2f2f2;

Step 5: Configure and Use the Directive

Finally, let’s invoke the directive in the HTML, as shown below.

<div class="container" ng-controller="TypeAheadController">
  <h1>TypeAhead Using AngularJS</h1>
  <typeahead items="items" prompt="Start typing a US state" title="name" subtitle="abbreviation" model="name" on-select="onItemSelected()" />


This tutorial has shown you how to create an AngularJS TypeAhead widget with configuration options. The complete source code is available for download on GitHub. Feel free to comment if something is unclear or you want to improve anything. Also, don’t forget to check out the live demo.

  • Bhuwan Arora

    Thanks Sandeep. Was really helpful and I am working to extend this further to keyboard navigation on the typeahead. Any idea how that can be done?

  • bindu

    i am trying with this example but its not working for me, i am not able to type anything

  • Sheetal

    Awesome illustration. I do have one question. How to get the ‘code’ value of the selected state within the controller?

  • Guest

    Where have you set selected property to false, when someone starts typing in the input field?

  • KK

    Where have you set selected property to false, when someone starts typing in the input text field?

  • Dhananjay Ghanwat

    Thanks for your directive, It works perfectly, can we also add Key board navigation to it, Currently we only have to select with mouse and cannot scroll with help of down or up keys

  • gai

    Hi i am new to angular and by using this, i could make a search nicely.but can’t figure out how to add keyboard navigation(arrow key select and enter key) for the items . any help?

  • Fahad

    Its good, but no support for keyboard.
    Please add if you can.
    Thanks for this.

  • Popat Shirsath

    Thanks Sandeep .Really it is good and very easy to integrate. In which I have integrated keyboard support same as Google mans Up, down key, escape and enter key Here is sample code . We need to add below code in link function of ‘typeahead’ directive

    scope.reset = function () {
    scope.current = 0;
    scope.selected = true; // hides the list initially
    } = function () {
    if (scope.items != null && scope.items != undefined && scope.items.length > 0) {
    var selectedItem = scope.items[scope.current];

    $(elem).bind(‘keydown’, function (evt) {
    if (scope.items == undefined) {
    if (scope.items.length == 0) {
    if (evt.which === 40) { // down key
    scope.current = (scope.current + 1) % scope.items.length;

    } else if (evt.which === 38) { // up key
    scope.current = (scope.current ? scope.current : scope.items.length) – 1;
    } else if (evt.which === 13 || evt.which === 9) { // 13- enter key
    scope.$apply(function () {;

    } else if (evt.which === 27) { // escape key
    scope.$apply(function () {

  • Joël

    Nice article. I have one question about this. How can you achieve when a click event outside the div.items element is captured the list will cleared. Maybe a mouseleave event (white same delay) is also nice to have. I can not figure it out on a nice and clean way.

  • Jose Jalandoni

    does anybody have any update on the keyboard functionality issue? please direct me on a solution. the suggestion of popat shirsath seems/sounds bit right, but can’t get it to work in the above code. Any more tips?

  • zorza

    The demo isn’t working

    • Nilson Jacques

      Hey zorza, sorry we missed your comment. The article has been updated with a working demo now.

  • Webmaster

    HI Sandeep, thanks for the tutorial. However, Its too slow with dynamic data especially large data set. Any advise on how to optimize the example?

  • Bla

    live demo: 404 not found

    • Nilson Jacques

      Thanks for letting us know. The link has been fixed :)

Get the latest in JavaScript, once a week, for free.