Angular multiselect dropdown

Home / Angular multiselect dropdown

angular_small
Mutliselect drop-downs are somewhat of a pain. It’s always nice when things are simple and discrete. But, the world is not simple and discrete. Life isn’t simple and discrete.

I find myself many times in need of a good, general purpose multiselect dropdown with checkboxes. There are many options for multiselect, like select2, but for some reason having discrete checkboxes is generally overlooked. This is where rolling my own came into play.

EDIT: Be sure to check out the updated info. for this directive. https://long2know.com/2016/05/angular-multiselect-dropdown-updated/


Once upon a time, before Angular, there was a jQuery multiselect plugin, with checkboxes, that I really enjoyed using. It was written by Eric Hynds, and I found myself using it on many projects a few years ago. After a bit of Googling, I also found a wrapper that one developer had started to make this plugin work with Angular. The problem I ran into is that the plugin itself was entirely intended to work with jQuery UI themes. Since every web project I’ve worked on in the past 2-3 years has been based on Bootstrap, using that old jQuery plugin, even with an Angular directive wrapping its functionality, was a non-starter.

My criteria/needs/desires for an Angular directive included most of the functionality provided in the old jQuery plugin: filtering, check/uncheck all, and Angular specifics like two-way binding, proper text labels based on selected items, and, of course, Bootstrap styling. Googling provided a good start rather than writing from scratch. I stumbled upon one directive (author unknown) that was pretty close, but it was based on older Bootstrap (2.3 I think), it was rough, and some of its features were broken. After a bit of retooling, and following the pattern (like it did) of Angular UI’s type-ahead textbox, things started to come together nicely.

In the first pass, I ran into scope issues. Multiple instances of the multiselect dropdown would persist changes across the different ngModels. After a bit of tweaking and comparing my code to Angular UI’s latest, I finally got those issues out of the way. It was also one of those odd weird side effects that I wasn’t sure why it was broken or what really fixed it. The other things that I tweaked were whether or not I wanted complex models, or discrete values, passed to the bound ngModel. Ultimately, I take the simple primitives and persist the selections in the URL using $location.search() in my controllers. Obviously, complex objects aren’t much use in this regard.

For the directive, utilization looks like this:

<div class="form-group">
    <multiselect class="input-xlarge multiselect" ng-model="vm.searchProp" options="p.key as p.value for p in vm.options" select-limit="5"
                    header="Select Stuff" selected-header="options selected" multiple="true" required complex-models="false" select-limit-use-filtered ="true"
                    enable-filter="true" filter-placeholder="Filter stuff..">
    </multiselect>
</div>

The basic attributes are pretty self explanatory. Options defines the expression by which the directive can determine its options to display. This is similar to an expression you’d pass to an ngRepeat. select-limit lets you limit how many items can be checked, multiple indicates whether or not multiple/single select is active, enable-filter indicates whether filtering of the options is available, and the rest of the attributes are mainly for determining what is displayed in the drop-down. It probably isn’t obvious, but select-limit-use-filtered determines whether Check/Uncheck and Check All/UncheckAll only work relative to the filtered selection and/or all items. This is handy in that if someone is looking at a filtered list of options, clicking ‘Check All’ only works on those options which are visible without affecting other options that were selected prior to filtering (ie – not visible). The same is true for ‘Uncheck All.’ The complex-models attributes is as I described previously. This determines whether primitive values (from your expression) are pushed to ngModel or the “complex” models are pushed.

While not pictured above, you can also add a ‘required’ attribute to tie into ngForm validation.

I won’t bore you with pasting a lot of code, since there is a fully working fiddle below from which you can view all of the code. You can also see how it works with multiple multiselect dropdowns in a single view.

The full source can be seen on Github.
There’s a plunk if you prefer Plunker and pen if your prefer Codepen.

18 thoughts on “Angular multiselect dropdown

  1. This is a pretty awesome directive, and I’ve to use it. Is it MIT/Apache licensed by any chance? Reason I ask is I’d like to use it at the company I work for, but they need a license file to point it.

  2. Firstly thanks for the awesome directive. Is there any way to get the deselected items collection back let’s say through any events subscription, so once the DropDown closes it will call the function in parent scope with the list of deselected items.

    1. It is possible. There are (2) ways the dropdown can close. You’ll find a “clickHandler” that is called when clicking outside of the multiselect which causes closing and a toggleSelect method which toggles the state of the dropdown through clicking the multiselect button. You would only need to pass in a callback handler/attribute and evaluate it in both of the popup closing routines. Alternatively, you can use the standard ng-change directive that will be triggered whenever the bound model is changed. If you want unselected items, it would be the items that are not in the selected list. A $filter could derive this. Let me know if that helps.

  3. It’s in the template within the two buttons:

    <li ng-show=\"multiple\">" +
      "<button type=\"button\" class=\"btn-link btn-small\" ng-click=\"checkAll()\"><i class=\"icon-ok\"></i> Check all</button>" +
       "<button type=\"button\" class=\"btn-link btn-small\" ng-click=\"uncheckAll()\"><i class=\"icon-remove\"></i> Uncheck all</button>" +
    "</li>"
    

    If you want it to be configurable, I can update the directive to support that feature through additional attributes.

    1. Yes, definitely. You pass your selected values as an array (if multiple) via ng-model. If you’re using “simple” models, it would be an array of scalar values representing your keys. If you’re using “complex” models, it would be an array of objects.

      As an example, look at the demo. The multiselect with the placeholder labled as “Select Stuff 3 Complex” has ng-model set to vm.option3 and the options are bound to vm.options2. If I wanted to set the 2nd item in the list as selected, I might do this:

      vm.option3 = [];
      vm.option3.push(vm.options2[1]);
      

      If I wanted to make all of this selected, I’d push every item onto the array:

      for (var i=0; i < vm.options.length; i++)
        vm.option3.push(vm.options2[i]);
      

      In either case, the options expression drives how the matching is made. It’s an interesting bit of code if you look at the “markChecked” methods and the initializing that parses the options expression to create the scope.items array.

Leave a Reply