Skip to content

AngularJS multi-select widget

2013 August 22
by Alec

In most web applications, there are entities that have many-to-many relationships. The most common example is a user-role scenario. In this case, there are typically many roles in a system, as well as many users, and each exist independent of the other. A user can be associated with zero or more roles, while a role can be applied to zero or more users.

So how do we present a user-friendly interface for modifying these relationships? This is a common problem for web applications, and there seems to exist a common interface with which most users are familiar. You simply present two lists of entities side-by-side, with “add” and “remove” buttons to move objects from one side to the other. The left side represents “selected” entities, and the right the “available” ones not already selected.

I wasn’t able to find an Angular directive implementing such an interface, so, naturally, I made my own. It relies on bootstrap for styling and uses a
<select type="multiple"> to represent each list of entities. Click “Result” below to see it in action:

The directive expects two arrays: An “available” array of entities, and “ng-model”, which should be a subset of those entities. These arrays don’t even need to have been initialized yet, the directive will wait until they are first initialized before creating the widget. Also, comparisons are done by reference, not equality, so the ng-model entities should be the same objects as those in “available.” This could easily be changed through usage of angular’s angular.equals method.

Try it out, let me know what you think, and fork away!

Update (Oct 7, 2013): I tweaked this to use a list of checkboxes rather than <select type="multiple">. This functions much better on mobile devices, and as arguably a better user experience overall. See the fiddle here.

Update (Jan 2, 2014): Added more flexibility for configuring the display of an item. The directive now accepts a simple expression (e.g. 'user as user | fullName') for the display attribute. Also, refactored the directive into its own module and added to GitHub.

17 Responses
  1. Mrunali permalink
    November 13, 2013

    Is this directive compatible with IE 8 in Compat mode?

  2. December 4, 2013

    Thanks for Sharing!

  3. March 21, 2014

    Is this compatible with AngularJS v1.2.13 ? I have try to implemet in my project and checking on check-box getting error like “Error: b is undefined”

    • Alec permalink*
      March 21, 2014

      Hi Pratik,

      Yes, it should be compatible with the latest Angular. Make sure angular is loaded BEFORE the multi-select module. If you post your code on jsfiddle I could take a look for you.

  4. dario permalink
    April 11, 2014

    Is it possible to order the items? When I move them they lost the order

    • aurel permalink
      August 14, 2015

      did you fix the ordering?

  5. Pedro permalink
    October 14, 2014

    Hi and thank you for sharing! This directive works very well with a manyToMany relationship and the records get stored in mySQL database smoothly 🙂

    However, I tried to move the template part of the directive out, and used templateUrl instead. This
    unfortunately did not work for me.

    Did I forget something here:

    Thanks in advandced

    • Alec permalink*
      October 15, 2014

      Hi Pedro, extracting the template should work fine. In fact, I did this in the latest version (see GitHub). Just override the

      angular.module("template/multiSelect.html", []).run(["$templateCache", function($templateCache) {
      '<div class="multiSelect">' + ...

      in your own app.

      You also forgot to include a couple files in your plunkr so it didn’t work. Try:

      • Pedro permalink
        October 17, 2014

        Thank you Alec! Its working now !

        Im using the directive from a modal registration form. The only thing that still is an issue is when I click the directive’s arrow-buttons from the modal; the modal dissapear without updating the form. 🙁

        But this is more a modal issue I think…

        Keep up with the good work Alec and thanks again for sharing 🙂

  6. Andrew permalink
    December 10, 2014

    When “model” and “available” load async, then model must be loaded first for correct work. I use nested http calls.

  7. Robert permalink
    March 19, 2015

    How do I increase the height of the select boxes, perfrably to make them adjust with the height of the page. I have used Chrome debugger to find out where the height of the boxes is set but can’t find anything.

    • Alec permalink*
      May 16, 2015

      Which boxes are you referring to? The headers?

  8. June 5, 2015

    How can I display the error message based on the ‘inputModel.$setValidity’ on the page which uses the multiselect component?

  9. Rocky permalink
    November 2, 2015

    Works well until Angular 1.3.0. the options are unselectable if an ng version above 1.3.0 used. Anyone faced any issues?

  10. bigtimechill permalink
    November 12, 2015

    Great tutorial, helped me a lot. I do have a question. Lets say you have a database that this is being stored into, how can you show the selected choices by default from a database.

    Lets say my database fields are stored in an ‘object’ and you want to display all the choices. Assuming I saved the choices as a TextField seperated by commas. E.g “Super Administrator, Admin, reg user”

    I tried this:
    new_placements = object.new_placement.split(“,”);
    new_placement_length = new_placements.length;
    for(var i=0; i < new_placement_length; i++){
    $scope.user = {
    siteId: i,
    placements: [new_placements[i]]

    I know the above won't work but I am just trying to figure out how to show already existing choices in the available roles select options.

    • Alec permalink*
      November 19, 2015

      I believe you could just use the new_placements array set to the ‘available’ attribute on your multi-select element.

Trackbacks and Pingbacks

  1. angularjs ng-repeat causing issues in ie9 - Tech Forum Network

Comments are closed.