如何防止$ setViewValue启动$ watch

时间:2014-08-05 03:05:44

标签: javascript jquery angularjs

我已经制定了一个包含jQuery插件的指令:

angular.module('ngJQueryPlugin', [])
  .controller('MainCtrl', function ($scope) {
    $scope.input = {'hexa': 0};
  })
  .directive('jQueryPlugin', function ($compile, $parse) {
      return {
        restrict: 'E',
        require: '?ngModel',
        link: function ($scope, $element, $attributes, ngModel) {
          if (ngModel === null) return;
          $scope.$watch($attributes.ngModel, function (hexaFromModel) {
            $element.data('jQueryPlugin').updateJQueryPluginUI(hexaFromModel);
          }, true);
          $element.jQueryPluginConstructor({}, function (hexaFromjQueryPlugin) {
            $scope.$apply(function () {
              ngModel.$setViewValue({ hexa: hexaFromjQueryPlugin});
              ngModel.$render();
            });
          });
        }
      };
    });

指令实例化:

<jquery-plugin ng-model="input" />
<input type="text" ng-model="input.hexa"/>

指令中的范围监视跟踪每个模型更新并通知jQueryPlugin。

jQueryPlugin构造函数中的第二个参数是每次jQueryPlugin UI更新十六进制颜色时调用的回调函数。然后它在范围上设置新值。

我的问题是$ setViewValue导致$ watch被调用。 $ watch更新了jQueryPlugin,这是没用的,因为我们最初从jQueryPlugin回调中得到通知。

我想到的解决方案是使用范围上的标志来了解更新最初是在jQueryPlugin或范围内启动的。这会阻止递归调用。

还有其他办法吗?例如,即使模型已更新,阻止$ setViewValue启动$ watch?

更新 我刚刚创造了一个plunker。 http://plnkr.co/edit/avumn9MJwdjmxHNtrmSF?p=preview 如果我在第一个输入(来自Angular)中输入文本,则会通知jQuery插件:正常行为。 如果我在jQuery组件的第二个输入中输入文本,则不应该通知它。

3 个答案:

答案 0 :(得分:0)

      $scope.$watch($attributes.ngModel, function (hexaFromModel, oldValue) {
        if (hexaFromModel === oldValue) {
          return;
        }

        $element.data('jQueryPlugin').updateJQueryPluginUI(hexaFromModel);
      });

请注意,这不会阻止手表被调用,但如果没有更改,则会停止监视逻辑运行。

答案 1 :(得分:0)

检测来自ngModel.$setViewValue()的更改的一种方法是与ngModel.$modelValue进行比较。你可以这样做:

$scope.$watch($attributes.ngModel, function (hexaFromModel) {
  if (angular.equals(hexaFromModel, ngModel.$modelValue)) {
    return;
  }

  $element.data('jQueryPlugin').updateJQueryPluginUI(hexaFromModel);
}, true);

或者您可能也可以使用$formatters而不是像这样观看$attributes.ngModel

ngModel.$formatters.push(function (value) {
  $element.data('jQueryPlugin').updateJQueryPluginUI(value);
});

希望这有帮助。

编辑:事实证明hexaFromModel实际上是一个对象,而不是一个简单的字符串,这使得上述解决方案毫无用处。

在这种情况下,如果更新来自插件,我想不出任何解决方案比设置标志绕过监听监听器更好。像这样:

var updatingByPlugin = false;

$scope.$watch($attributes.ngModel, function(newValue, oldValue) {
  if (updatingByPlugin) {
    return;
  }

  jQueryPlugin.update(newValue);
}, true);

jQueryPlugin = new jQueryPluginConstructor($element, {}, function(hexFromjQueryPlugin) {
  console.log('notified');

  updatingByPlugin = true;

  $scope.$apply(function () {
    ngModel.$setViewValue({
      hex: hexFromjQueryPlugin
    });
  });

  updatingByPlugin = false;
});

示例plunker: http://plnkr.co/edit/HH1lw7Pk31tsyri9h0w7?p=preview

答案 2 :(得分:0)

你应该写一个$ render函数,而不是调用它:

angular.module('ngJQueryPlugin', [])
  .controller('MainCtrl', function ($scope) {
    $scope.input = {'hexa': 0};
  })
  .directive('jQueryPlugin', function ($compile, $parse) {
      return {
        restrict: 'E',
        require: '?ngModel',
        link: function ($scope, $element, $attributes, ngModel) {
          if (ngModel === null) return;
          ngModel.$render = function () {
                 if(ngModel.$viewValue)
                     $element.data('jQueryPlugin').updateJQueryPluginUI(ngModel.$viewValue);
          }
          $element.jQueryPluginConstructor({}, function (hexaFromjQueryPlugin) {
            $scope.$apply(function () {
              ngModel.$setViewValue({ hexa: hexaFromjQueryPlugin});
            });
          });
        }
      };
    });