5

谁能告诉我是否可以在自定义 Angular 指令的控制器中要求和使用 ngModel。我试图远离链接功能。我看到大多数示例都使用链接功能,但我认为必须有某种方法可以在指令控制器中使用它?还是只能在链接功能中访问?我见过的一种方法,如下所示,给了我不确定的。不知道有没有别的办法??我正在尝试验证组件并在错误对象上设置无效类。

//directive
angular.module('myApp', [])
  .directive('validator', function (){
    return {
      restrict: 'E',
      require: {
           ngModelCtrl: 'ngModel',
           formCtrl: '?^form'
      },
      replace: true,
      templateUrl: 'view.html',
      scope: {},
      controllerAs: 'ctrl',
      bindToController: {
         rows: '=',
         onSelected: '&?' //passsed selected row outside component
         typedText: '&?' //text typed into input passed outside so developer can create a custom filter, overriding the auto
         textFiltered: '@?' //text return from the custom filter
         ngRequired: "=?" //default false, when set to true the component needs to validate that something was selected on blur. The selection is not put into the input element all the time so it can't validate based on whether or not something is in the input element itself. I need to validate inside the controller where I can see if 'this.ngModel' (selectedRow - not passed through scope) is undefined or not.
      },
      controller: ["$scope", "$element", function ($scope, $element){
         var ctrl = this;
         ctrl.rowWasSelected;

         //called when a user clicks the dropdown to select an item
          ctrl.rowSelected = function (row){
               ctrl.rowWasSelected = true;
               ctrl.searchText = row.name; //place the name property of the dropdown data into ng-model in the input element
          }

         ctrl.$onInit = $onInit;
         function $onInit (){
             ctrl.ngModelCtrl.$validators.invalidInput = validate;            
          }

        function validate (modelValue, viewValue) {
             var inputField = ctrl.formCtrl.name;
             var ddField = ctrl.formCtrl.listData;

             inputField.$setValidity('invalidInput', ddField.$touched && ctrl.rowWasSelected);

            return true;
          }          
       }];
   }
});

//template
<form name="validatorForm" novalidate>
  <div class="form-group" ng-class="{ng-invalid:validatorForm.name.$error.invalid}">
     <label for="name">Names</label>
     <input type="name" class="form-control" name="name" placeholder="Your name" ng-change="typedText(text)" ng-model="ctrl.textFiltered" ng-blur="ctrl.validate()" ng-required="ctrl.ngRequired">
  </div>
  <ul ng-show="show list as toggled on and off" name="listData" required>
    <li ng-repeat="row in ctrl.rows" ng-click="ctrl.rowSelected({selected: row}) filterBy:'ctrl.textFiltered' ng-class="{'active':row === ctrl.ngModel}">{{row}}<li>
  </ul>
</form>

//html
<validator
   rows="[{name:'tim', city:'town', state:'state', zip: 34343}]"
   on-selected="ctrl.doSomethingWithSelectedRow(selected)"
   typed-text="ctrl.manualFilter(text)"
   text-filtered="ctrl.textReturnedFromManualFilter"
   ng-required="true">
</validator>
4

2 回答 2

1

这是代码重构了一下(注意:您需要使用最新的 Angular 来完成其中的一些)。重新阅读您的问题后,我不确定您到底遇到了什么问题(无论是如何在指令定义对象中使用 required 还是如何使用 ngRequired 属性或其他)。请注意,使用下面的代码,您不需要 $scope:

angular.module('myApp', []);
angular.module('myApp').directive('validator', validator);

function validator (){
    return {
        restrict: 'E',
        require: {
            ngModelCtrl: 'ngModel'
        },
        replace: true,
        templateUrl: 'view.html',
        scope: {}, //this controls the kind of scope. Only use {} if you want an isolated scope.
        controllerAs: 'ctrl',
        bindToController: {
            rows: '=',
            onSelected: '&?', //passsed selected row outside component
            typedText: '&?', //text typed into input passed outside so developer can create a custom filter, overriding the auto
            textFiltered: '@?', //text return from the custom filter
            ngRequired: "=?" //default false, when set to true the component needs to validate that something was selected on blur. The selection is not put into the input element all the time so it can't validate based on whether or not something is in the input element itself. I need to validate inside the controller where I can see if 'this.ngModel' (selectedRow - not passed through scope) is undefined or not.
        },
        controller: 'validatorController'
    }
}

//usually do this in a new file

angular.module('myApp').controller('validatorController', validatorController);
validatorController.$inject = ['$element'];

function validatorController($element){
    var ctrl = this;

    //controller methods
    ctrl.validate = validate;

    ctrl.$onInit = $onInit; //angular will execute this after all conrollers have been initialized, only safe to use bound values (through bindToController) in the $onInit function.

    function $onInit() {
        if(ctrl.ngRequired)
            ctrl.ngModelCtrl.$validators.myCustomRequiredValidator = validate;
    }



    //don't worry about setting the invalid class etc. Angular will do that for you if one if the functions on its $validators object fails
    function validate (modelValue, viewValue){
        //validate the input element, if invalid add the class ng-invalid to the .form-group in the template
        //return true or false depending on if row was selected from dropdown
        return rowWasSelected !== undefined
    }
}   

以下是 Angular 的 $compile 文档中的一些片段:

如果 require 属性是一个对象并且 bindToController 是真的,那么所需的控制器将使用 require 属性的键绑定到控制器。此绑定发生在所有控制器都已构建之后但在调用 $onInit 之前。

弃用警告:虽然非 ES6 类控制器的绑定当前在调用控制器构造函数之前绑定到 this,但现在不推荐使用这种用法。请改为将依赖于绑定的初始化代码放在控制器上的 $onInit 方法中。

同样,请确保您使用的是最新版本的 Angular,否则上述内容将无法正常工作。我不记得究竟是哪一部分(我觉得它可能正在将要求对象键自动绑定到控制器对象),但我肯定遇到了一个讨厌的错误,上面的方法不起作用,我使用的是 1.4 .6.

第二次编辑:只是想澄清一些事情:

1) .ng-invalid 类将应用于任何无效的角度验证形式的输入。例如,如果输入上有必需的属性并且输入为空,则输入将具有 ng-invalid 类。此外,它将有一个 .ng-invalid-required 类。输入上的每个验证规则都有自己的 ng-invalid 类。你说你想在第一次模糊后为输入添加红色边框。执行此操作的标准方法是使用如下 CSS 规则:

.ng-invalid.ng-touched {
   border: 1px #f00 solid;
}

如果您检查经过验证的输入,您将看到各种角度类。其中之一是.ng-touched。被触摸的元素是至少被模糊一次的元素。如果您想确保验证仅适用于模糊,您可以使用 ng-model-options 指令。

2) $formatters 用于格式化模型值。Angular 有两种方式的数据绑定。这意味着角度是 $watching 模型值和视图值。如果其中一个发生变化,则执行一个工作流来更新另一个。工作流程如下:

视图值更改 -> $parsers -> $validators -> 更新模型值模型值更改 -> $formatters -> 更新视图值

工作流的结果被填充到另一个值中。这意味着如果您想在视图中显示模型值之前更改它(也许您想格式化日期),那么您可以在 $formatter.xml 中进行。然后,您可以在 $parser 返回模型时执行相反的操作。当然,当你编写 $validators 时,你应该知道 $parsers 中发生了什么,因为它是在发送到模型之前得到验证的解析视图值。

3) 根据我从 Angular 文档中添加的引用,很明显,您不应使用任何包含由 $onInit 之外的 bindToController 绑定到控制器的值的逻辑。这包括 ngModelCtrl。请注意,只要您确定另一个函数将在 $onInit 之后执行,您就可以将逻辑放在另一个函数中。

4) 这里有两件事要考虑:哪个控件出现错误,以及您从哪里触发验证。听起来您想从下拉列表的工作流程中触发它(即在它被模糊一次之后)。所以,我建议在下拉列表中添加一个验证器。现在,您说您要验证输入而不是下拉列表。因此,您可以在验证器中使用 $setValidity。为确保下拉菜单始终“有效”,您只需从验证器返回 true。你说你只想在模糊后验证。有两种方法可以做到这一点(在我的脑海中)。一种是使用我上面提到的ng-model-options,另一种是测试下拉菜单是否在验证器中被$touched。这是使用第二种方法的一些代码:

function validate (modelValue, viewValue) {
    var inputField = ctrl.formCtrl.inputName, ddField = ctrl.formCtrl.ddName;

    inputField.$setValidity('validationName', ddField.$touched && rowSelectedCondition);
    return true;
}

你看,在我设置有效性之前,我正在测试下拉菜单是否已被 $touched(即模糊)。这两种方法之间存在根本区别。使用 ng-model-options 基本上将整个更新工作流程推迟到模糊之前。这意味着您的模型值只会在输入模糊后更新以匹配视图值。第二种方式(使用 $touched)将在每次 viewValue 更改时进行验证,但只会在第一次模糊后使输入无效。

如果输入无效,'validationName' 参数将仅指定添加的类,因此在这种情况下,它将添加两个类 .ng-invalid(添加到任何无效控件)和 .ng-invalid-validation-name。

为了访问 formCtrl,您需要向您的 require 对象添加另一个属性(formCtrl:'^form')

于 2016-02-21T13:48:15.320 回答
0

在自定义指令中访问 ngModel 提供的信息的最简单方法是将范围设置为 false。这应该默认发生,但如果您正在使用多个指令,则明确设置它会有所帮助。这样,指令将继承控制器和控制器别名,就好像它完全原生于视图的其余部分一样。

指令:

.directive('myValidator', function (){
return {
  restrict: 'E',
  replace: true,
  templateUrl: 'view.html',
  scope: false
  };
}

您不必对模板进行太多更改。只需确保 ng-model="ctrl.name" 绑定到您的主控制器上的某些东西,或者您在视图的其余部分使用的任何控制器。您也可以将验证功能移至主控制器。或者,到服务并注入控制器等。

在自定义指令中使用 compile 或 link 可以使其更加通用。但您基本上是在传递指令、属性或 html 标记的值。ngModel 可用,但您可能不会在每次使用自定义指令时都使用 ctrl.user。编译或链接让您在每次使用指令时设置 ngModel 的值。

于 2016-02-21T04:36:28.083 回答