Give us a call +386 1 542 06 00

Razum

Get a proposal

Blog

David Stutz’s Bootstrap Multiselect is probably the most popular Bootstrap plugin for rendering a select element with multiple attribute as a Bootstrap button with a dropdown menu. It is feature rich and works great, but if you want to use it with Angular, there is unfortunately no directive available for it.

Using Bootstrap Multiselect with Angular by Damir

David Stutz’s Bootstrap Multiselect is probably the most popular Bootstrap plugin for rendering a select element with multiple attribute as a Bootstrap button with a dropdown menu. It is feature rich and works great, but if you want to use it with Angular, there is unfortunately no directive available for it.

Writing a feature complete Angular directive for Bootstrap Multiselect is quite an undertaking, which could also be a reason why there is none available. However, if you only plan to use it yourself, you can make your job much easier by only supporting the subset of features you actually need. That is what we did for a project we are currently working on.

Converting a regular select element into a Bootstrap Multiselect is simply a matter of calling the plugin’s multiselect method on the element. This can easily be achieved with a very basic Angular directive:

app.directive('multiSelect', function() {
      
        function link(scope, element) {
          element.multiselect();
        }
      
        return {
          restrict: 'A',
          link: link
        };
      });
      

This will invoke the required function on the element once it is initialized by Angular. The directive is restricted to attributes, according to its intended use:

<select multiple="multiple" multi-select>
        <optgroup label="Vegetables">
          <option value="tomatoes">Tomatoes</option>
          <option value="mushrooms">Mushrooms</option>
          <option value="onions">Onions</option>
        </optgroup>
        <optgroup label="Other">
          <option value="cheese">Cheese</option>
          <option value="mozarella">Mozzarella</option>
          <option value="pepperoni">Pepperoni</option>
        </optgroup>
      </select>
      

To support different configuration options, the directive could be extended with support for binding an options object. You can avoid that, though, as long as you can settle with all instances having the same configuration. In this case, you can simply hardcode the options into the directive. E.g. this would make optgroups clickable:

function link(scope, element) {
        var options = {
          enableClickableOptGroups: true
        };
        element.multiselect(options);
      }
      

Basic support for binding the selection to a model is already built into Angular:

<select ng-model="selection" multiple="multiple" multi-select>
        <!-- available options -->
      </select>
      

For most cases this works great, but not with clickable optgroups. Angular doesn’t update the model in this case, which brings it out of sync until an option is selected or deselected individually. Of course, this makes the binding useless.

A quick look at Bootstrap Multiselect documentation can bring us closer to solving the issue. Whenever an optgroup is clicked, onChange function will be triggered if set. Inside it, we need to notify Angular of the change. By consulting the select directive source code, we can see that invoking change on the select element will achieve exactly that:

function link(scope, element) {
        var options = {
          enableClickableOptGroups: true,
          onChange: function() {
            element.change();
          }
        };
        element.multiselect(options);
      }
      

There is another aspect to selection model binding: setting the selection based on the model value. Again, Angular select directive can take care of that itself. Unfortunately, any changes to the select element are not automatically visible in the button and the dropdown menu. Instead, Bootstrap Multiselect provides .multiselect('refresh') method that needs to be called in order to update its selection to the current state of the select element.

For the binding to work correctly, this method will need to be called every time Angular updates the select element based on the model changes. To modify the behavior of the built-in select directive in such a way, a decorator must be used. Official documentation includes an example of ngHref directive decorator, which demonstrates the basic idea: provide an alternative compile function to replace the original link with a modified one.

Unlike href directive, select directive defines both preLink and postLink, therefore the decorator’s compile method will need to do the same. Only postLink needs to be modified, though. This is where $render function is defined, which takes care of updating the element with model data. The decorator will provide its own $render method that will first call the original one, immediately followed by a call to .multiselect('refresh'):

app.config(['$provide', function($provide) {
        $provide.decorator('selectDirective', ['$delegate', function($delegate) {
          var directive = $delegate[0];
      
          directive.compile = function() {
      
            function post(scope, element, attrs, ctrls) {
              directive.link.post.apply(this, arguments);
      
              var ngModelController = ctrls[1];
              if (ngModelController && attrs.multiSelect !== null) {
                originalRender = ngModelController.$render;
                ngModelController.$render = function() {
                  originalRender();
                  element.multiselect('refresh');
                };
              }
            }
      
            return {
              pre: directive.link.pre,
              post: post
            };
          };
      
          return $delegate;
        }]);
      }]);
      

As you can notice, an additional check is performed before replacing the $render function: multi-select attribute must be present. This makes sure that Bootstrap Multiselect has been initialized for the element. Otherwise, all select elements would automatically be affected.

For convenience, here is a fully working embedded Plunk:

< Back to article list