Custom dropdown/auto-complete with AngularJS

Update (2015-09-17): Check out new Version 2 of this control.

I’ll admit it, I’m a big fan of AngularJS; it’s a great framework. If you consider yourself a serious front-end developer, you should really know about AngularJS. In our day to day work, we frequently find challenging requirements and problems that need to be solved efficiently and, why not, elegantly. And frameworks such as AngularJS help us achieve just that, and even turn our solutions into generic pieces of work that we can put in our tool box and use here and there across different projects. This time I will talk briefly about one such case. Hopefully it can be of use to someone else, too.

The problem

We all need to use an auto-complete box once in a while. There are many good solutions out there that are easy to use. But… they are usually limited to working with strings. On the other hand, regular select elements are great when you want to display names but use Id’s (or some sort of unique value) for your model and your business rules. But… they are pretty inflexible and “boring”. So, I needed and auto-complete box that worked also well with complex objects, and could integrate seamlessly with AngularJS.

The solution

Create an AngularJS directive from scratch. At least that was the only option I saw. And it didn’t turn out too bad after all. So here it is, Custom Select (sorry for the totally uncreative name).


<div custom-select ng-model="fruit" ng-options="f for f in fruits">
</div>

You may have noticed that the attributes look very similar to those of a regular AngularJS select directive (except for the additional custom-select attribute). Assuming you setup the appropriate values in your scope, you’ll get something like this.

Untitled

If you want to use JSON objects and have a display property and another property that gets saved to your model, you could use something like this:


<div custom-select ng-model="state" ng-options="s.id as s.name for s in states">
</div>

The end result would look very similar, but will work slightly different.

As a bonus, you could use a custom template for your dropdown items. Here’s an example:


<div custom-select ng-model="person" ng-options="t as t.name for t in people">

<div class="pull-left" style="width: 40px">
        <img ng-src="{{ t.picture }}" style="width: 30px" />
    </div>


<div class="pull-left">
        <strong>{{ t.name }}</strong>
        <span>{{ t.phone }}</span>
    </div>


<div class="clearfix"></div>

</div>

And the end result:
Untitled

If you liked it, you can grab the source code and the examples from Github.

79 thoughts on “Custom dropdown/auto-complete with AngularJS

  1. Great job! I use AngularJS in my personal web applications, it’s amazing web framework, I’ll try your component, thanks!

  2. Is it possible to make it two way databinding? If I set the model ($scope.fruit=’orange’ it doesn’t update the selected item in the dropdown

  3. hi
    I want use you pretty control but I have a problem.
    My project has used Bootstrap3 but custom-select work with Bootstrap 2.
    Can you help me?

      • It doesn’t seem to work when the list is inside an object. For example:
        $scope.growable = { options: [‘Item 1’, ‘Item 2’, ‘Item 3’] };

        But when using:
        $scope.growable = [‘Item 1’, ‘Item 2’, ‘Item 3’];

        works fine.
        Any ideas of why?

  4. Hi Buddy ,
    How to Clear the Selected Value when I press Reset ? Can you Help me in this ?
    and
    Is it Possible to set Values Dynamically to the Custom Select ?

    • You can clear the selected value by setting the ng-model property to null or undefined (e.g. $scope.fruit = null). If by the second question you mean making the option list dynamic, yes, you can.

      • Thanks for your early response.Yeah it’s working fine.
        Could you show me any sample example, so that i can understand.

    • I just modified the sample index file and added a Reset button to one of the examples. There’s also one example where you can add items to the dropdown model by using $scope.growable.push. It also works if you do something like $scope.growable = someOtherValue.

      • Hi buddy ,
        I have a problem here .
        $scope.reset=undefined is Working fine with Reset button . But if i click on the Submit Button , after completing all my necessary things with that scope value . its not Resetting any Value ..

    • If you followed the example, $scope.reset is the name of the function that is called when you click on the button. Make sure you’re setting your ng-model property to undefined, not the function. You will need to call $scope.reset (or whatever your function name is) manually after submitting the form.

  5. Axel, Congratulations for this!
    I’m trying to use it here on my app and I found a problem on line 98(dropdownElement.eq(0).dropdown()), “undefined is not a function”. This error does not happen everytime. I’ve noticed that the problem is because of the dropdown function. Is there any library you used that I should import or put on my definitions to make this work 100% all the time?

    • Hi, Fernando.
      The only two dependencies: jQuery and Bootstrap (more importantly, the dropdown plug-in). Make sure you are referencing the jQuery and bootstrap JavaScript files before this control and your own JavaScript code.

      • Axel, I had a long time without this problem but suddenly it happened again this week.
        I changed your line 98:
        anchorElement = dropdownElement.eq(0).dropdown(),
        for:
        anchorElement = $(dropdownElement.eq(0)).dropdown(),
        Fortunately when I was debugging this new line the error “behavior” showed up again and with this new line it worked fine. Now it is working 100% all the time.!

      • My first thought is that you are referencing the jQuery script after the AngularJS script. When Angular initializes, since it doesn’t “see” jQuery, it uses its own implementation of jqLite. The DOM elements that Angular sends in the directives are wrapped by this jqLite object, so the jQuery plugins are not available. In the line you changed, you are explicitly wrapping the element in a jQuery object, so you have access to the Bootstrap dropdown plugin. Try moving the jQuery script tag above the AngularJS one and see what happens.

  6. Hi ,
    How to set a Default value to this Custom Select . I tried something Like this $scope.ngModelOfCombo=someValue; But is not setting by default to the Combo. Suggest me some methods .

    • You just need to specify the default ng-model value in your controller. For example, if you setup your Custom Select like so

          <div custom-select ng-model="fruit" ng-options="f for f in fruits"></div>
      

      You need to initialize your ng-model variable to whatever initial value you would like, for example $scope.fruit= 'orange'.

  7. Hi Axel,

          Congratulations  very good work. I have small problem when i implemented this one. If the user select any item,then select is disappearing how to show the 'select' option in the dropdown after select the item.
    
    • Hi, usually that’s the desired behavior (once the user selects an item, the box displays the name of the item so the user knows what is selected). You would need to modify the source code to make it work like you want.

  8. Hi Axel,
    Good work on this one.
    I just have a question here, I am having trouble integrating it to my application.
    I’m getting
    Error: dropdownElement.eq(…).dropdown is not a function

  9. Hi Axel,

    I am getting error
    TypeError: undefined is not a function
    at module.directive.link (http://localhost:8080/TargetModel/ui/js/mr/ng-widgets/custom-select/customSelect.js:98:44)
    for line
    anchorElement = dropdownElement.eq(0).dropdown(),

    of
    // Create dropdown element
    var dropdownElement = angular.element(dropdownTemplate),
    anchorElement = dropdownElement.eq(0).dropdown(),
    inputElement = dropdownElement.eq(1).find(‘:text’),
    ulElement = dropdownElement.eq(1).find(‘ul’);

    Could you please suggest what possibly can be wrong ?

  10. Hello,

    It’s a really nice directive.
    I have a question? Can you help me please to add a value in the dropdown to clear the selected option?
    Kind of like having a value with the functionality of the reset button.

    Thank you.

    • Hi, I’m not sure I understand your question. If you need to reset the selected value you only have to set your ng-model variable to whatever value you need (empty, undefined, or any other default value). This should be done on your controller.

      • When there is no value selected, a “Select…” option appears as default(placeholder).
        When I select a value, i need to have a “Select…” option at the beginning to reset the ng-model.

    • I think I get it now. In that case, you would need to manually insert that option at the beginning of your options list. For example:

      $scope.states = [
          { id: null, name: 'Select...' },
          { id: 'AL', name: 'Alabama' },
          { id: 'AK', name: 'Alaska' },
          { id: 'AS', name: 'American Samoa' },
          ...
          ];
      
  11. Awesome plugin Man. Just one thing can we do grouping like for example
    India
    . Kolkata
    . Delhi
    United Stated
    . New York
    . Atlanta
    Australia
    . Melbourne
    . Sydney
    . Parth

  12. After spending a few hours trying to make this work with Angular in a template with what little instructions you’ve given, I’ve given up on it! Going to try Select2 now!

  13. Hi Axel,

    Thanks for writing this wonderful component.

    I am trying to pre-select an option from the drop down. Following is modified version of your complex object selection example which is not working for me as model itself is an complex object.

    $scope.state = { id: ‘AL’, name: ‘Alabama’ };

    Any suggestions how this can be fixed?

    • The problem is that object equality for objects is given by reference, and for “primitives”, by value. In other words, the first example works because it is plain strings, so if you set $scope.fruit = 'mango', the code will find that one of the elements in the array of values equals 'mango' (by value); but your example with objects is not working because you are creating a new object instance, whose reference won’t equal any of the objects in the array. One way to accomplish what you want is doing something like $scope.state = $scope.states[0].
      As a side note, Angular can compare objects by value, but that involves a higher performance cost since the objects are traversed and tested for value equality.

  14. I am having an application which shows suggestions as we enter the text in the textbox. When I am entering ‘!’ i.e exclamation mark in the text box instead of showing error of special characters, it is considering as “NOT” and displaying results except that particular text. For Eg. If I write “!S” in the textbox it is showing me the results except “S” instead it should throw an error of special characters.

    Kindly provide me with a solution.

    • Hi Ishan,
      The custom select delegates the filtering functionality to the filter filter (documentation). This filter handles the leading ! as a negation and as of now there is no way to configure or disable that behavior. You would need to implement your own filtering logic and plug it into the custom select directive.

  15. I can’t make it work… =[

    I’m using angularjs 1.4 (then, cs-options) jquery 2.1.1, bootstrap 3.3.4…

    The error is in customselect.js at line 100

    var dropdownElement = angular.element(dropdownTemplate),
    anchorElement = dropdownElement.eq(0).dropdown(), // — Hi, I’m line 100 !
    inputElement = dropdownElement.eq(1).find(‘:text’),
    ulElement = dropdownElement.eq(1).find(‘ul’);

    The dropdown method does not exist…

    Somebody can help me ?

  16. Pingback: Custom dropdown/auto-complete with AngularJS (Version 2) | Axel Zarate
  17. Hi, I’m using your autocomplete, but I must add new item to the select list and set this item as selected after some async function. Can you help me?

    • Hi, I’m assuming that your function is asynchronous but the items are stored in an array a variable and the filtering is performed locally. If that’s the case, I suggest you download the latest version from the repository (a new option async was added) and do something along the lines of:

      <div custom-select="i for i in items | filter: { name: $searchTerm }" ng-model="selected">
      </div>
      
      $scope.items = [/*Initialize your items array*/];
      
      // Callback executed after your async function is completed
      function myAsyncFunctionCallback(result) {
          $scope.items.push(result);
          $scope.selected = result;
      }
      

      The important parts here are the items array and the selected property.

      • Hi,
        Thank you for the help. The new version fixed the adding of new item to the list, even without the async option. Now I can see it there but the label doesn’t update unless I click the select again. I am using custom onAdd function.
        My code is as follows :
        $scope.itemsList.push(data.newItem);
        $scope.supplyItem.itemId = data.newItem.itemId;

        <

        div custom-select=”item.itemId as item.name for item in itemsList | filter: $searchTerm” ng-model=”supplyItem.itemId”

        The select ng-model is “supplyItem.itemId” and is set to the new value.

  18. Any reason why angular.module(‘ui.filters’, []); is not included in format.js?

    I see that you have included this line in your sample.html. These customizations can be if we have control over source inclusion in html file.

    In our project all the dependencies are added by gulp. EX:
    …..
    http://bower_components/jquery/dist/jquery.js
    http://bower_components/lodash/dist/lodash.compat.js
    http://bower_components/bootstrap/dist/js/bootstrap.js
    http://bower_components/angular/angular.js
    http://bower_components/angular-route/angular-route.js
    http://bower_components/utilities/dist/bower/utilities.js
    http://bower_components/js-custom-select/js/format.js
    http://bower_components/js-custom-select/js/customSelect.js
    …….

    From your source:
    https://ajax.googleapis.com/ajax/libs/angularjs/1.2.21/angular.min.js

    // This is needed in order to use the format filter
    angular.module(‘ui.filters’, []);

    http://js/format.js
    http://js/customSelect.js

    How do you think we can use your plug-in?

    • Yes, you’re right. I used that little directive just for the formatting of the messages, but for those same reasons I decided to just include the relevant function inside the directive itself (it’s pretty straightforward). That dependency has been removed for recent versions. Please take a look at the link at the top of the article.

  19. Hi Axel,

    Thank you for the custom dropdown, it’s working fine.
    The is it possible to implement a custom filter, i.e. like in your example «Custom Item Template»,
    to search for name but also for phone, or more attributes?
    Thank you for your time!

  20. Thank you for the custom dropdown. I want to know, how to change the default ‘displayText’ value for different dropdowns. Actually I have 3-4 dropdowns. I want to show like “Select Fruits”, “Select Vegetables”. etc.

    • You can pass options to each custom select directive by using the custom-select-options attribute in your HTML:

      <div custom-select="f in fruits | filter: $searchTerm" custom-select-options="fruitsOptions"></div>
      

      And in your controller:

      $scope.fruitsOptions = {
          displayText: "Select Fruits"
      };
      
    • You can plug in your own (or someone else’s) custom filter inside the logic. For example, you can take a look at this Stack Overflow answer on how to provide your own comparator to filter by the start of the string. Then, for the custom-select attribute you would need to specify something like f for f in fruits | filter:$searchTerm:startsWith.

  21. Hey,
    It’s a good directive, but I’m noticing – in loading multiple cutom-selects in a modal window, that this hangs..

    so i have 3 sections specifically where 1 custom-select is rendered in a data-ng-repeat.

    so customers, manufacturers, products (this is in an edit/new order partial)…
    it hangs. it renders eventually, but it’s a delay of a few seconds, which seems odd to me, yet when I load the attrs to the console they’re instantly rendered on modal load.

    The scope array objects are pre-loaded on module load (each section is it’s own module and pre-loads all the assets, ie: customers, manufacturers, products based upon user login) so theoretically this shouldn’t hang because the assets are there – no xhr is required. any ideas on this?

      • I will if the option doesn’t function. I realized by reading the documentation that I can load the custom-select with the search functioning as a typeahead – which might solve my problem.

        I didn’t realize the entire ul/li was compiled rather than simply referenced from the scope – yet you also have an xhr call – so I’m going to try that first and hopefully load multiple key/value pairs – if this presents the same problem I’ll fiddle it and we can take a look🙂

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s