Angularjs Custom select2 directive Angularjs Custom select2 directive angularjs angularjs

Angularjs Custom select2 directive


It might be simpler than you expected!

Please have a look at this Plunker

Basically, all plugins, Angularjs $watch need to be based on something. I'm not 100% sure for jQuery-select2; but I think that's just the control's normal DOM events. (And in the case of Angular $watch, it is a "dirty checking loop")

My idea is that let's trust jquery-Select2 and AngularJS for handling those change events.

We just need to watch for change in Angular's ways and update the select in Select2's ways

var refreshSelect = function() {    if (!element.select2Initialized) return;    $timeout(function() {        element.trigger('change');    });};//...scope.$watch(attrs.ngModel, refreshSelect);

Notice: I have added in 2 new watch which I think you would like to have!


I'm not that familiar with select2 (so the actual API for getting and setting the displayed value in the control may be incorrect), but I suggest this as an alternative:

app.directive("select2",function($timeout){    return {        restrict: 'AC',        require: 'ngModel',        link: function(scope, element, attrs, model) {            $timeout(function() {                element.select2();            });            model.$render = function() {                element.select2("val",model.$viewValue);            }            element.on('change', function() {                scope.$apply(function() {                    model.$setViewValue(element.select2("val"));                });            })        }    };});

The first $timeout is necessary because you are using ng-options, so the options won't be in the DOM until the next digest cycle. The problem with this is that new options won't be added to the control if the countries model is later changed by your application.


Angular is not going to like have model data modified by a third party plugin. My guess based on the fact that your using $timeout is there is a race condition between Angular updating the options or the model and the select2 plugin. The solution I came up with is to take the updating mostly out of Angular's hands and do it manually from the directive, that way you can ensure everything is matching no matter who is modifying. Here's the directive I came up with:

app.directive("select2",function($timeout,$parse){    return {        restrict: 'AC',        link: function(scope, element, attrs) {            var options = [],                el = $(element),                angularTriggeredChange = false,                selectOptions = attrs["selectOptions"].split(" in "),                property = selectOptions[0],                optionsObject = selectOptions[1];            // watch for changes to the defining data model            scope.$watch(optionsObject, function(n, o){                var data = [];                // format the options for select2 data interface                for(var i in n) {                    var obj = {id: i, text: n[i][property]};                    data.push(obj);                }                el.select2({data: data});                // keep local copy of given options                options = n;            }, true);            // watch for changes to the selection data model            scope.$watch(attrs["selectSelection"], function(n, o) {                // select2 is indexed by the array position,                // so we iterate to find the right index                for(var i in options) {                    if(options[i][property] === n) {                        angularTriggeredChange = true;                        el.val(i).trigger("change");                    }                }            }, true);            // Watch for changes to the select UI            el.select2().on("change", function(e){                // if the user triggered the change, let angular know                if(!angularTriggeredChange) {                     scope.$eval(attrs["selectSelection"]+"='"+options[e.target.value][property]+"'");                    scope.$digest();                }                // if angular triggered the change, then nothing to update                angularTriggeredChange = false;            });        }    };});

I've added to attributes select-options and select-model. These will be used to populate and update the data using select2's interface. Here is a sample html:

<select id="sel" class="form-control" select2 name="country"  select-selection="client.primary_address.country"   select-options="name in client.countries" >     <option value="">Select Country</option></select><div>Selected: {{client.primary_address.country}}</div>

Please note there's still some cleanup that could be done to the directive and there are any things at assumes about the input, such as the "in" in the select-options attribute. It also doesn't enforce the attributes but just fails if they don't exist.

Also please note that I've used Select2 version 4, as evidenced by the el.val(i).trigger("change"). You may have to revert some things if using an older version.

Here is the jsfiddle demo of directive in action.