ngModel - How to deal with its different behavior in different browsers? ngModel - How to deal with its different behavior in different browsers? angularjs angularjs

ngModel - How to deal with its different behavior in different browsers?


This seems to be related to http://bugs.jqueryui.com/ticket/8878

As pointed out in the above link, the change event is triggered only in Firefox and not in Chrome. So in your case, $setViewValue is again triggered when clicked outside and the model value is set to "Apple".

There is change callback for autocomplete jquery ui widget. To handle both the case/browsers may be you would have to explicitly set view value again on this call back (and it works).

http://plnkr.co/edit/GFxhzwieBJTSL8zjSPSZ?p=preview

    link: function(scope, elem, attrs, ngModel) {      elem.on('change', function(){      // This will not be printed in Chrome and only on firefox      console.log('change');    });    select: function(event, ui) {      ngModel.$setViewValue(ui.item.data.id);      scope.$apply();    },    // To handle firefox browser were change event is triggered    // when clicked outside/blur    change: function(event, ui) {      ngModel.$setViewValue(ui.item.data.id);      scope.$apply();    }


Ok, I think I've made it. The solution is based on Yoshi's comment and it uses local model to keep selected data.

When user selects something, local model is set to selected Object and $viewValue is set to the text value of selected item. Then parser sets id property of local model as $modelValue.

select: function(event, ui) {  if(ui.item && ui.item.data){    model = ui.item.data    ngModel.$setViewValue(model.name);    scope.$apply();  }}ngModel.$parsers.push(function(value) {  if(_.isObject(model) && value!==model.name){    model = value;    return model;  }  return model.id;});

Parser function do also one important thing. Because it's run when user type something or on the change event (that was the problem in firefox!), it checks if the value is same as current local model's text value, and if not it changes local model into this value. It means that if parser function is run by change event value would be the same as text value, so $modelValue is not changed, but if user type something model is updated to the typed value (it becomes String).

Validator function checks if local model is an Object. If not it means that field is invalid so by default its $modelValue disappears.

Here is the plunkr:http://plnkr.co/edit/2ZkXFvgLIwDljfJoyeJ1?p=preview

(In formatter function I return that what comes, so $viewValue is temporarily an Object but then in $render method I call $setViewValue to set $viewValue and $modelValue correctly, so it becomes String. I heard $setViewValue should not be run in $render method, but I don't see other way to set correct $modelValue when something comes from outside).


I had similiar fights with the ngModelController and $setViewValue.

Eventually I looked for alternative solutions. One approach that I found which worked pretty well was to create a new element as component directive which includes the input tag as a transcluded element.

app.directive('fruitAutocomplete', function($http) {  return {    restrict: 'E',    require: 'ngModel',    transclude: true,    template: '<ng-transclude></ng-transclude>',    link: function(scope, elem, attrs, ngModelController) {      var $input = elem.find('input');      $input.autocomplete({        ...      });    }  }})

In the HTML:

<fruit-autocomplete name="fruit" ng-model="model.fruit">  <input ng-disabled="inputDisabled" placeholder="input fruit"/></fruit-autocomplete>

Here is a working Plunker

With this proposed solution you can isolate the ngModelController and the jQueryUI modal interplay to its own custom element and it does not interfere with the "normal" <input> tag and you are not concerned by the jQueryUI bug.

By using the <input> tag as transcluded element you can still benefit from most the Angular input goodies like ng-disabled, placeholder, etc...