Damir Arh Damir Arh - 5 months ago 20
AngularJS Question

How to invoke a JQuery method after Angular select directive applies a model change?

I am trying to change the selection of David Stutz's bootstrap-multiselect via Angular

ng-model
:

<select ng-model="selection" multiple="multiple" id="my-example">
<option value="cheese">Cheese</option>
<option value="tomatoes">Tomatoes</option>
<option value="mozarella">Mozzarella</option>
<option value="mushrooms">Mushrooms</option>
<option value="pepperoni">Pepperoni</option>
<option value="onions">Onions</option>
</select>


The changes to the model only get applied to the underlying
select
element, but the bootstrap-multiselect doesn't get updated automatically. Looking at its documentation, this is expected: you are required to call
multiselect('refresh')
afterwards to propagate the changes:

$('#my-example').multiselect('refresh');


My question is:


How to invoke this method when the model changes after Angular is done updating the
select
element?


Since I need to access the element, I assume directives are the way to go. I was looking at decorators, which in theory I could use to modify the behavior of the built-in select directive, but I don't know how to get my code invoked at the right moment.

I've prepared a plunk to demo the issue:


  • I have two multiselects bound to the same model: a bootstrap-multiselect and a plain one

  • I initialize both with a default selection

  • The first button changes the selection. The plain multiselect is updated immediatelly, but the bootstrap-multiselect appears unchanged.

  • The second button shows the current model value in an alert.

  • The third button calls
    refresh
    on bootstrap-multiselect, which causes it to update. This is what I would like to get called automatically by Angular.


Answer

In the end I managed to solve my problem with a decorator. I based it on the Directive Decorator Example in AngularJS documentation.

Unlike ngHrefDirective from the example, selectDirective defines both preLink and postLink, therefore the compile override must also return both. I only needed to change postLink though, where $render is defined. In my version of the method I simply invoked the original method, which updates the select element, and called multiselect('refresh') afterwards, which was my original requirement:

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) {
          originalRender = ngModelController.$render;
          ngModelController.$render = function() {
            originalRender();
            element.multiselect('refresh');
          };
        }
      }

      return {
        pre: directive.link.pre,
        post: post
      };
    };

    return $delegate;
  }]);
}]);