AngularJS select 当模型值不在选项中时不会出错

AngularJS select doesn't error when model value not in options

Angular 1.4.8.

我有这个 2 个字母的美国州代码标记:

<div class="form-group" ng-class="{'has-error': form.licenseState.$invalid }">
    <label for="licenseState" class="control-label">License State</label>
    <select name="licenseState" id="licenseState" class="form-control" required
            ng-model="ctrl.student.License.State"
            ng-options="key as key for (key, value) in ctrl.licenseFormats">
    </select>
</div>

当模型值加载为空白时,这正确地给我一个错误。

但是,当模型值加载为不在列表中的值(错误数据)时,它不会出错。

考虑到结果是所有级别的错误信息,这种情况让我感到困惑。用户看到一个似乎有效的空白值(尽管 required 属性)。模型看到与用户不同的值,该值似乎有效,即使它不在有效值列表中。

我错过了什么吗?是否有适当的(规范的angular)方法使此触发器成为表单验证错误?


更新

根据@paul147 的回答,我最终得到了这个可重复使用的指令,用于根据列表验证模型值。

m.directive('validValues', function() {
    return {
        scope: {
            validValues: '='
        },
        restrict: 'A',
        require: 'ngModel',
        link: function (scope, element, attributes, ngModel) {
            var values = angular.isArray(scope.validValues)
                ? scope.validValues
                : Object.keys(scope.validValues);
            ngModel.$validators.validValues = function (modelValue) {
                return values.indexOf(modelValue) !== -1;
            }
        }
    }
});

用法示例:

<select name="licenseState" required valid-values="ctrl.licenseFormats"
        ng-model="ctrl.student.License.State"
        ng-options="key as key for (key, value) in ctrl.licenseFormats">
</select>

我简要探索了直接从 select 元素获取选项,但 ngOptions 将值置于自定义格式中。 F.ex string:AL(实际上是查找键)为AL的值。进一步讨论 。最终,如果我只是像上面那样使用有效值的冗余声明(在本例中为 ctrl.licenseFormats),那么该指令可以更轻松地重用。

要触发 ngModel 指令错误,您可以做的一件事是向 ngModel 的 $validators 添加自定义验证器。

ngModel 的值在更改时通过 $validators 传递,如果验证器 returns false 则会引发错误。

一个示例实现是向模型的元素添加自定义指令,并在该指令中定义验证器:

这是一个带有工作示例的 plunkr:http://plnkr.co/edit/m6OygVR2GyMOXTTVTuhf?p=preview

// markup
<select name="licenseState" id="licenseState" class="form-control" required
  ng-model="student.License.State"
  ng-options="key as value for (key, value) in licenseFormats"
  check-state
  license-formats="licenseFormats">

// in the controller
$scope.licenseFormats = {
  'OR': 'Oregon', 
  'WA': 'Washington', 
};     

// the directive
app.directive('checkState', function() {
  return {
    scope: {
      licenseFormats: '='
    }, 
    restrict: 'A', 
    require: 'ngModel', 
    link: function(scope, element, attributes, ngModel) {

      // defining the validator here
      ngModel.$validators.state = function(modelValue) {
        return Object.keys(scope.licenseFormats).indexOf(modelValue) > -1;
      }
    }
  }
});