【问题标题】:Angular validation issues with complex model复杂模型的角度验证问题
【发布时间】:2016-07-14 12:24:06
【问题描述】:

我的设置:Angular 1.4.4

我有一个看起来像这样的简单表格:

<div ng-controller="MyCtrl3">
  <datepick ng-model="model.carA" foo3="model.carB"></datepick>

  <datepick ng-model="model.carB" foo3="model.carA"></datepick>
  <pre> {{ model | json }} </pre>
</div>

datepick 指令如下所示:

myApp.directive('datepick', function() {
  return {
    restrict: 'E',
    scope: {
      ngModel: '='
    },
    template: '<div ng-if="true"><input type="text" ng-model="ngModel.bar"/></div>'
  };
});

所以我使用ng-model 属性将具有bar 属性的对象传递给它。到目前为止,一切都按预期工作。模型与呈现形式的变化同步。

下一步我想引入验证以验证当前指令的基础模型上的属性 bar 以及 MyCtrl3 - model 中的一些其他对象。为此,我创建了属性指令foo3

myApp.directive('foo3', function() {
  return {
    restrict: 'A',
    require: "ngModel",
    link: function(scope, element, attrs, controller) {

      scope.$watch(attrs.foo3, function(newValue, oldValue) {
        // New Value of comparison field
        console.log("New valued for comparison model: " + JSON.stringify(newValue));
        // Current value of undelying movelValue
        console.log("Current value for undelying model" + JSON.stringify(controller.$modelValue));

        controller.$validate();
      }, true);

      var validateMoreThanDate = function(modelValue, viewValue) {
        let viewValueObject = modelValue;
        var comparisonModel = scope.$eval(attr.moreThanDateObject);

        if ((!viewValueObject && !viewValueObject.bar) ||
          (!comparisonModel && !comparisonModel.bar)) {
          // It's valid because we have nothing to compare against
          return true;
        }
        // It's valid if model is lower than the model we're comparing against
        return viewValueObject.bar > comparisonModel.bar;
      };

      controller.$validators['moreThanDateObject'] = validateMoreThanDate;
    }
  };
});

如您所见,我将模型作为参数传递给指令foo3。观察者跟踪每一个变化,但模型变化时不会触发验证器。

问题:如何验证我的案例中的这些字段? (这里正在使用 jsFiddle 检查实时代码:https://jsfiddle.net/ichyr/b1jqfkj1/

注意我知道当对象中的模型和该对象的属性发生变化时,不会触发 $parsers 和 $formatters 管道:

如果新值是一个对象(而不是字符串或数字),我们 应该在将对象传递给 $setViewValue 之前制作对象的副本。 这是因为 ngModel 不执行对象的深度监视,它 只寻找身份的改变。如果只更改属性 对象然后 ngModel 将不会意识到该对象有 已更改并且不会调用 $parsers 和 $validators 管道。

所以也许 $validators 也没有被触发,但是在 $watch 表达式中添加 controller.$validate() 并没有帮助。

【问题讨论】:

    标签: javascript angularjs forms validation


    【解决方案1】:

    我已经使用 $watchers 成功地完成了这项任务,而没有使用任何 $parsers、$formatters 或 $validators。如果属性发生变化,前两个肯定不适用于复杂模型。最后一个似乎也不起作用。

    我通过ng-modelfoo3 为我在datepick 指令中引用的两个模型创建了两个手表+ 创建了小的验证函数。所以foo3 验证属性指令现在看起来像这样:

    myApp.directive('foo3', function() {
      return {
        restrict: 'A',
        require: "ngModel",
        link: function(scope, element, attrs, controller) {
    
          scope.$watch(attrs.foo3, function(newValue, oldValue) {
                    var isValid = validateMoreThanDate(controller.$modelValue, newValue);
            controller.$setValidity('moreThanDateObject', isValid);
          }, true);
    
           scope.$watch(attrs.ngModel, function(newValue, oldValue) {
                    var isValid = validateMoreThanDate(newValue, scope.$eval(attrs.foo3));
            controller.$setValidity('moreThanDateObject', isValid);
          }, true);
    
          var validateMoreThanDate = function(underlyingModel, comparisonModel) {
            if ((!underlyingModel && !underlyingModel.bar) ||
              (!comparisonModel && !comparisonModel.bar)) {
              // It's valid because we have nothing to compare against
              return true;
            }
            // It's valid if model is lower than the model we're comparing against
            return underlyingModel.bar > comparisonModel.bar;
          };
        }
      };
    });
    

    我还创建了一个小的装饰指令,用于格式化和解析输入自身中的值(测试类型导致字符串值):

    // Directive to format the data in the text inputs
    myApp.directive('numberInputParser', function() {
      return {
        restrict: 'A',
        require: "ngModel",
        link: function(scope, element, attrs, controller) {
          debugger;
          controller.$parsers.unshift(function(data) {
            return parseInt(data);
          });
    
          controller.$formatters.unshift(function(data) {
            debugger;
            return data;
          });
        }
      };
    });
    

    datepick 指令的代码也相应更改(添加了number-input-parser 指令):

    myApp.directive('datepick', function() {
      return {
        restrict: 'E',
        scope: {
          ngModel: '='
        },
        template: '<div ng-if="true"><input type="text" ng-model="ngModel.bar" number-input-parser/></div>'
      };
    });
    

    结果代码可以在这里看到:https://jsfiddle.net/ichyr/f1qeey37/

    【讨论】:

      【解决方案2】:

      注意我的同事提出了更好的方法。

      这个解决方案可以改进。我们可以在datepick 指令中使用带有自定义getter/setter 函数的人工对象来省略$watchers 的使用,如下所示:

      myApp.directive('datepick', function() {
        return {
          restrict: 'E',
          scope: {
            ngModel: '='
          },
          template: '<div ng-if="model.open"><input type="text" ng-model="model.value"/></div>',
          link: function(scope) {
            scope.model = {
              get value() {
                return scope.ngModel;
              },
              set value(newValue) {
                scope.ngModel = parseInt(newValue);
              },
              open: true
            };
          }
        };
      });
      

      这克服了ng-if 范围内变量的阴影,因为我们在那里传递对象,而不是原始的。

      这种方式带来以下好处:

      1. 无需将对象传递给datepick 指令
      2. 删除了几个 $watch 表达式,因为我们正在利用原生 JS 功能(从而提高性能)
      3. 允许使用原语作为 datepicker 的参数(模型),从而可以利用 angular.js $parsers$formatters$validators(它们不起作用/检测对象属性的变化/如果模型是对象)
      4. getter 和 setter 不能用于代替 angular $parsers$formatters

      您可以在这里找到生成的小提琴:https://jsfiddle.net/ichyr/suncp2gf/

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-01-14
        • 1970-01-01
        • 1970-01-01
        • 2016-03-03
        相关资源
        最近更新 更多