Angular directive doesn't fire change event in ngModel

6k Views Asked by At

I created a directive for input elements which creates a custom select2 dropdown.
After selecting an element, the original input field (which is used to filter data via the ngModel) is filled with the selected data from the dropdown, but the change event of the input isn't fired.
If I change the input value by hand, the filter is working.

Here is the code of my directive.

.directive('ajaxSelect2', ['$timeout', function($timeout) {
    return {
        restrict: 'A',
        link: function (scope, element, attrs) {
            switch(element.prop("tagName")) {
                case 'INPUT':
                    $timeout(function() {
                        element.select2({
                            [ ... ] //the select2 part
                        });
                    });
                break;
            }

            /* update the input value */
            element.bind("change", function(e) {
                scope.$apply(function() {
                    scope[attrs.ngModel] = e.val;
                });
            });

            scope.$watch(element, function () {
                console.log('x'); //not called
            });
        }
    }
}])

I thought the element.bind("change") in the scope.apply() would trigger a view update, but it doesn't work. Does anybody have an idea how to tell the ngModel it has new data?

EDIT: I figured out, the problem is the ngModel. Because I have to filter several values, my Model is filter.foobar. This is not working. If I change the model to foobar, it will work. I created a fiddle to illustrate: http://jsfiddle.net/StinsonMaster/6t3Nt/3/

2

There are 2 best solutions below

0
On BEST ANSWER

I have implemented a custom select2 dropdown directive. And register the change event handler to element which directive link to. The change event of the input element will be triggered when I selected a item from dropdown list. Everything works as expected. You could try my implementation:

Directive

.directive('ajaxSelect2',['$timeout', function($timeout){
  return {
    restrict: 'A',
    link: function(scope, element, attrs){
        element.select2({
            width: '200px',
            createSearchChoice:function(term, data) { if ($(data).filter(function() { return this.text.localeCompare(term)===0; }).length===0) {return {id:term, text:term};} },
            multiple: true,
            data: [{id: 0, text: 'story'},{id: 1, text: 'bug'},{id: "ccc", text: 'task'}]
        });         
        element.bind("change",function(e){
            scope.$apply(function(){
                scope[attrs.ngModel] = e.val.toString();
            });
        });
    }
  };
}]);

HTML

<body ng-app="selectApp">
  <div ng-controller="selectCtrl">
    <span>Selcted value: {{input}}</span>
    <div>
      <input id="test2" ng-model="input" ajax-select2/>
    </div>
  </div>
</body>

Here is the JSBin DEMO

[EDIT] For Property access problem

The ng-model of custom select2 element is "filter.foo". You want to notify the angular world that the value of ng-model have been updated by select2 dropdown filter. But you can't access the second level property like this:

scope[attrs.ngModel] = e.val;  // equal to scope['filter.foo'] which is the first level property 'filter.foo'

You should access the property in this way:

var props = attrs.ngModel.split(".");
if(props.length>1){
  scope[props[0]][props[1]] = e.val;  //[] equals to dot '.'
}else{
  scope[attrs.ngModel] = e.val;
}

Here is a jsFiddle demo

Hope this is helpful.

0
On

As element is not part of the scope try with using a function returning it instead:

scope.$watch(function () {
  return element;
}, function () {
  console.log('x');
});