NgModel - Как бороться с его различным поведением в разных браузерах?

Я пытаюсь разобраться с различным поведением ngModel в разных браузерах.

Моя директива обертывает jqueryUI autocomplete и в событии select она вызывает ngModel.$setViewValue(selectedItem.id). Autocomplete позволяет пользователю выбирать элемент с помощью мыши или нажатием enter на клавиатуре.

Если предложенный пункт:

{
  "name": "Apple",
  "id": "1000"
}

Я ожидаю, что после его выбора значением ngModel будет выбран элемент id - 1000.

  • В Chrome он работает нормально - он правильно устанавливает $viewValue и $modelValue ($modelValue=1000).

  • В Firefox он устанавливает модель как в Chrome ($modelValue=1000), но когда я нажимаю где-то еще - размываю (тогда браузер, вероятно, запускает событие change), модель изменяется и становится такой же, как видимое входное значение ($modelValue='Apple').

  • В IE 11 он устанавливает правильную модель только тогда, когда я выбираю элемент с щелчком мыши. Если я выберу его, нажав enter, модель станет видимым входным значением ($modelValue='Apple')

Вот plunkr: http://plnkr.co/edit/o2Jkgprf8EakGqnpu22Y?p=preview

Я хотел бы достичь такого же поведения в каждом браузере. Как бороться с этими проблемами?

Ответы

Ответ 1

Хорошо, я думаю, что сделал это. Решение основано на комментарии Yoshi и использует локальную модель для хранения выбранных данных.

Когда пользователь выбирает что-то, для локальной модели установлено значение Object, а $viewValue - текстовое значение выбранного элемента. Затем парсер устанавливает свойство id локальной модели как $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 также делает одну важную вещь. Поскольку он запускается, когда пользователь вводит что-то или в событии change (это была проблема в firefox!), Он проверяет, совпадает ли значение с текущим значением текстовой локальной модели, и если оно не изменяет локальную модель на это значение. Это означает, что если функция парсера запускается change, значение события будет таким же, как текстовое значение, поэтому $modelValue не изменяется, но если пользователь вводит что-то модель, обновляется до типизированного значения (оно становится String).

Функция Validator проверяет, является ли локальная модель Object. Если это не означает, что поле недействительно, поэтому по умолчанию его $modelValue исчезает.

Вот plunkr: http://plnkr.co/edit/2ZkXFvgLIwDljfJoyeJ1?p=preview

(В функции форматирования я возвращаю то, что приходит, поэтому $viewValue временно Object, а затем в методе $render я вызываю $setViewValue правильно установить $viewValue и $modelValue, поэтому он становится String Я слышал, что $setViewValue не должен запускаться в методе $render, но я не вижу другого способа установить правильный $modelValue, когда что-то приходит извне).

Ответ 2

Это похоже на http://bugs.jqueryui.com/ticket/8878

Как указано в приведенной выше ссылке, событие изменения запускается только в Firefox, а не в Chrome. Таким образом, в вашем случае $setViewValue снова запускается при нажатии на него, а для значения модели установлено значение "Яблоко".

Существует обратный вызов изменения для автозаполнения jquery ui widget. Возможно, для обработки обоих случаев/браузеров вам придется явно задать значение представления снова при этом обратном вызове (и он работает).

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();
    }

Ответ 3

У меня были сходные бои с ngModelController и $setViewValue.

В конце концов я искал альтернативные решения. Один из подходов, который я нашел, который работал довольно хорошо, заключался в создании нового элемента в качестве директивы компонента, который включает в себя входной тег как передаваемый элемент.

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({
        ...
      });
    }
  }
})

В HTML:

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

Вот рабочий Plunker

С помощью этого предлагаемого решения вы можете выделить ngModelController и модальный интерфейс jQueryUI в свой собственный пользовательский элемент, и он не мешает "нормальному" тегу <input>, и вы не обеспокоены ошибкой jQueryUI.

При использовании тега <input> в качестве элемента transcluded вы по-прежнему можете использовать большинство входных свойств Angular, таких как ng-disabled, placeholder и т.д.