Angular - 需要ngModel并在自定义指令的控制器中使用它,而不是链接功能

时间:2016-02-21 04:07:24

标签: javascript angularjs angularjs-directive angular-ngmodel angularjs-validation

有人可以告诉我是否可以在自定义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>

2 个答案:

答案 0 :(得分:1)

这是重构的代码(注意:你需要使用最新的Angular来实现其中一些)。在重新阅读您的问题之后,我不确定您究竟遇到了什么问题(无论是如何在指令定义对象中使用必需或如何使用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类控制器   目前在控制器构造函数之前绑定到此   在调用时,此用法现已弃用。请放置初始化代码   依赖于控制器上的$ 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-model-options指令。

2)$ formatters用于格式化模型值。 Angular有双向数据绑定。这意味着angular是$观看模型值和视图值。如果其中一个更改角度执行工作流程以更新另一个。工作流程如下:

查看值更改 - &gt; $ parsers - &gt; $ validators - &gt;更新模型值 模型值变化 - &gt; $ formatters - &gt;更新视图值

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

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

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

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

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

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

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

要访问formCtrl,您需要在require对象中添加另一个属性(formCtrl:&#39; ^ form&#39;)

答案 1 :(得分:0)

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

指令:

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

您不必非常更改模板。只需确保ng-model =&#34; ctrl.name&#34;绑定到主控制器上的某些内容,或者用于视图其余部分的任何控制器。您也可以将验证功能移动到主控制器。或者,对服务并注入控制器等

在自定义指令中使用编译或链接可以使它更加通用。但是你基本上传递了指令,属性或html标签的值。 ngModel可用,但每次使用自定义指令时都可能没有使用ctrl.user。通过编译或链接,您可以在每次使用指令时设置ngModel的值。