How to retain Angular model and watch when using JQuery UI plugin Selectize

All we need is an easy explanation of the problem, so here it is.

Plunker: http://plnkr.co/edit/ElXFi2mo44VpLVsaooOJ

I am modifying a working web app to utilize a jQuery UI plugin called Selectize. Previously I had an input element bound to the controller and a watch placed on that variable. I added the required code to selectize the component which has undone my watch and binding because this plugin modifies the DOM elements and obscures my bound element with new elements.

I would prefer to stay with the angular watch rather than calling a method in selectize to watch the value.

Comment out lines 7-16 to see that the watch is called correctly on every input change.

<input id="itemQuery" type="text" placeholder="Search" class="form-control" ng-model="myValue">

And the script:

angular.module('Sample.controllers', [])
    .controller('mainController', ['$scope', 
        function($scope) {
            $scope.myValue="";
            $('#itemQuery').selectize({
                delimiter: ',',
                persist: false,
                create: function(input) {
                    return {
                        value: input,
                        text: input
                    }
                }
            });

            $scope.$watch('myValue', function(newValue, oldValue) {
               alert("Old value: " + oldValue + " New value: " + newValue);
            });
}]);
angular.module('Sample', ['Sample.controllers']);        

How to solve :

I know you bored from this bug, So we are here to help you! Take a deep breath and look at the explanation of your problem. We have many solutions to this problem, But we recommend you to use the first method because it is tested & true method that will 100% work for you.

Method 1

First thing you can do is avoid implicit DOM manipulation inside controller and write a directive for that instead.

Updated Demo

App.directive('sampleSelectivize', function() {
  return {
    restrict: 'A',
    link: function(scope, element, attrs) {
      element.selectize({
          delimiter: ',',
          persist: false,
          create: function(input) {
            return {
              value: input,
              text: input
            }
          }
      }).on('change', function(event) {
        console.log($(this).val());
      });
    }
  };
})

And apply it to your input

<input sample-selectivize id="itemQuery" />

If you’ve checked the documentation, there are different events can be helpful for you
https://github.com/brianreavis/selectize.js/blob/master/docs/events.md

Method 2

Thanks to codef0rmer for pointing me in the right direction. The solution was to tell angular that the scope needed updating and to provide it with the new value for this components. The key part being that I needed to include require: '?ngModel' in my directive initializers and then angular provided it as the 4th parameter to the link function.

angular.module('Sample.controllers', [])
        .controller('mainController', ['$scope',
          function($scope) {
            $scope.myValue = "";
            $scope.$watch('myValue', function(newValue, oldValue) {
              console.log("OldValue: " + oldValue + " New value: " + newValue);
            });
          }]).directive('sampleSelectivize', function() {
  return {
    restrict: 'A',
    require: '?ngModel',
    link: function(scope, element, attrs, ngModel) {
      element.selectize({
        delimiter: ',',
        persist: false,
        create: function(input) {
          return {
            value: input,
            text: input
          }
        }
      }).on('change', function(event) {
        scope.$apply(applyChange);
      });
      function applyChange() {
        ngModel.$setViewValue(element.context.value);
      }
    }
  };
});
angular.module('Sample', ['Sample.controllers']); 

I found this resource to be helpful though incomplete: http://docs.angularjs.org/api/ng.directive:ngModel.NgModelController

Solution plunk http://plnkr.co/edit/ieqQRWBub8ZJ8zOdEhEs?p=preview
Note: It uses console.log rather than alert.

Note: Use and implement method 1 because this method fully tested our system.
Thank you 🙂

All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply