Подтвердить что ты не робот

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

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

Мои модульные тесты прошли отлично, потому что они смотрят только на модельное значение. Однако на практике DOM не обновляется, когда триггеры "возвращают". Наше лучшее предположение заключается в том, что установка старого == 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';
}
4b9b3361

Ответ 1

Наше лучшее предположение заключается в том, что установка старого == 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 script, чтобы использовать приведенный выше код.

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>