AngularJS. Почему в директиве, которая изменяет значение модели, я должен вызывать $render?

Я создал директиву, предназначенную для прикрепления к элементу с помощью директивы ngModel. Если значение модели соответствует чему-то, значение должно быть установлено на предыдущее значение. В моем примере я ищу «foo» и возвращаю его к предыдущему, если это то, что введено.

Мои модульные тесты прошли нормально, потому что они рассматривают только значение модели. Однако на практике DOM не обновляется, когда срабатывает «возврат». Наше лучшее предположение здесь заключается в том, что установка old == new предотвращает грязную проверку. Я прошел через метод $setViewValue, и, похоже, он делает то, что должен. Однако он не будет обновлять DOM (и то, что вы видите в браузере), пока я явно не вызову ngModel.$render() после установки нового значения. Он отлично работает, но я просто хочу посмотреть, есть ли более подходящий способ сделать это.

Код ниже, вот скрипка с тем же самым.

angular.module('myDirective', [])
    .directive('myDirective', function () {
    return {
        restrict: 'A',
        terminal: true,
        require: "?ngModel",
        link: function (scope, element, attrs, ngModel) {
            scope.$watch(attrs.ngModel, function (newValue, oldValue) {
                //ngModel.$setViewValue(newValue + "!");   

                if (newValue == "foo")
                {
                    ngModel.$setViewValue(oldValue);   
                    /* 
                        I Need this render call in order to update the input box; is that OK?
                        My best guess is that setting new = old prevents a dirty check which would trigger $render()
                    */
                    ngModel.$render();
                }
            });
        }
    };
});

function x($scope) {
    $scope.test = 'value here';
}

person David Peters    schedule 03.10.2013    source источник


Ответы (1)


Наше лучшее предположение состоит в том, что установка old == new предотвращает грязную проверку.

Слушатель-наблюдатель вызывается только тогда, когда значение выражения, которое он прослушивает, изменяется. Но поскольку вы изменили модель обратно на ее предыдущее значение, она больше не будет вызываться, потому что это похоже на то, что значение вообще не изменилось. Но будьте осторожны: изменение значения свойства внутри наблюдателя, отслеживающего это же свойство, может привести к бесконечному циклу.

Однако он не будет обновлять DOM (и то, что вы видите в браузере), пока я явно не вызову ngModel.$render() после установки нового значения.

Правильно. $setViewValue устанавливает значение модели, как если бы оно было обновлено представлением, но вам нужно вызвать $render, чтобы эффективно отобразить представление на основе (нового) значения модели. Посетите это обсуждение для получения дополнительной информации.

Наконец, я думаю, что вы должны подойти к своей проблеме по-другому. Вы можете использовать свойство $parsers для NgModelController для проверки пользовательского ввода вместо использования наблюдателя:

link: function (scope, element, attrs, ngModel) {
  if (!ngModel) return;

  ngModel.$parsers.unshift(function(viewValue) {
    if(viewValue === 'foo') {                 
      var currentValue = ngModel.$modelValue;
      ngModel.$setViewValue(currentValue);
      ngModel.$render(); 
      return currentValue;
    }
    else 
      return viewValue;
  });
}

Я изменил ваш скрипт jsFiddle, чтобы использовать приведенный выше код.

angular.module('myDirective', [])
.directive('myDirective', function () {
  return {
    restrict: 'A',
    terminal: true,
    require: "?ngModel",
    link: function (scope, element, attrs, ngModel) {
      if (!ngModel) return;

      ngModel.$parsers.unshift(function(viewValue) {
        if(viewValue === 'foo') {                 
          var currentValue = ngModel.$modelValue;
          ngModel.$setViewValue(currentValue);
          ngModel.$render(); 
          return currentValue;
        }
        else 
          return viewValue;
      });
    }
  };
});

function x($scope) {
  $scope.test = 'value here';
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<h1>Foo Fighter</h1>
I hate "foo", just try and type it in the box.
<div ng-app="myDirective" ng-controller="x">
  <input type="text" ng-model="test" my-directive>
  <br />
  model: {{test}}
</div>

person Michael Benford    schedule 03.10.2013
comment
Спасибо, добавление $parser (или $formatter?) имеет гораздо больше смысла, чем то, что я делал. Однако у меня возникают проблемы с модульным тестированием; мне нужно вызвать дополнительный метод Angular, чтобы начать процесс синтаксического анализа? В настоящее время, когда мой модульный тест манипулирует значением модели, новый обработчик анализатора не срабатывает. - person David Peters; 04.10.2013
comment
$parser используется для проверки того, что исходит от представления, а $formatter используется для форматирования модели перед ее визуализацией. Если вы хотите запретить кому-либо добавлять foo непосредственно в модель, то использование синтаксического анализатора не сработает. - person Michael Benford; 04.10.2013
comment
Что касается тестирования, в основном вы должны инициировать событие input в поле ввода, где применяется директива ng-model. Этого будет достаточно, чтобы Angular запустил ваш код. Если вам нужен пример, создайте еще один вопрос, и я отвечу на него за вас. Я боюсь, что это загромождает комментарии, если я помещу это здесь. - person Michael Benford; 04.10.2013
comment
Нашел ваш другой answer/fiddle по теме, отлично работает. Спасибо еще раз! - person David Peters; 04.10.2013
comment
О, я не помню этот вопрос. Рад, что ты нашел это. Возможно, вам будет интересно ознакомиться с тесты, которые я написал для директивы, о которой говорил в этом посте. - person Michael Benford; 04.10.2013