简体   繁体   English

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

[英]Angular - Requiring ngModel and using it in controller of custom directive, not link function

Can anybody tell me if it's possible to require and use ngModel inside the controller of a custom Angular directive. 任何人都可以告诉我是否可以在自定义Angular指令的控制器中要求和使用ngModel。 I'm trying to stay away from the link function. 我试图远离链接功能。 I see most examples use the link function but I'm thinking there must be some way to use it inside a directive controller? 我看到大多数例子使用链接函数,但我认为必须有一些方法在指令控制器中使用它? Or is it only accessible in a link function? 或者它只能在链接功能中访问? The one way I have seen to do it, as show below, gives me undefined. 我所看到的一种方式,如下所示,给了我一些未定义的方法。 I'm not sure if there is another way?? 我不确定是否还有其他方法? I'm trying to validate the component and have the invalid class be set on the error object. 我正在尝试验证组件并在错误对象上设置了无效的类。

//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>

Here is the code refactored a bit (Note: you need to be using the latest Angular for some of this). 这是重构的代码(注意:你需要使用最新的Angular来实现其中一些)。 After rereading your question I am not sure what exactly you are having trouble with (whether it is how to use required in the directive definition object or how to use ngRequired attribute or something else). 在重新阅读您的问题之后,我不确定您究竟遇到了什么问题(无论是如何在指令定义对象中使用必需或如何使用ngRequired属性或其他内容)。 Note that with the code below you do not need $scope: 请注意,使用下面的代码,您不需要$ 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
    }
}   

Here are a couple of snippets from Angular's docs on $compile: 以下是来自Angular的$ compile文档的几个片段:

If the require property is an object and bindToController is truthy, then the required controllers are bound to the controller using the keys of the require property. 如果require属性是一个对象而bindToController是真实的,则所需的控制器使用require属性的键绑定到控制器。 This binding occurs after all the controllers have been constructed but before $onInit is called. 在所有控制器构造之后但在调用$ onInit之前发生此绑定。

and

Deprecation warning: although bindings for non-ES6 class controllers are currently bound to this before the controller constructor is called, this use is now deprecated. 弃用警告:虽然在调用控制器构造函数之前,非ES6类控制器的绑定当前已绑定到此,但现在不推荐使用此用法。 Please place initialization code that relies upon bindings inside a $onInit method on the controller, instead. 请放置依赖于控制器上$ onInit方法内的绑定的初始化代码。

Again, make sure you are using the latest version of Angular or the above won't work. 再次确保您使用的是最新版本的Angular,或者上述版本无法使用。 I can't remember exactly which part (I feel like it might be getting the require object keys auto-bound to the controller object), but I have definitely run into a nasty bug where the above wasn't working and I was using 1.4.6. 我不记得确切的哪个部分(我觉得它可能会让需求对象键自动绑定到控制器对象),但我肯定遇到了一个令人讨厌的错误,上面的内容不起作用,我使用的是1.4 0.6。

Second Edit: Just want to clear up a few things: 第二次编辑:只想清理一些事情:

1) the .ng-invalid class will be applied to any input in an angular validated form that is invalid. 1).ng-invalid类将应用于无效的角度验证表单中的任何输入。 For example, if there is a required attribute on an input and the input is empty, then the input will have an ng-invalid class. 例如,如果输入上有必需属性且输入为空,则输入将具有ng-invalid类。 Additionally, it will have a class .ng-invalid-required. 此外,它将具有类.ng-invalid-required。 Every validation rule on the input gets its own ng-invalid class. 输入上的每个验证规则都有自己的ng-invalid类。 You say you want to add a red border to an input after it has been blurred for the first time. 您说您想在第一次模糊后为输入添加红色边框。 The standard way to do this is to have a css rule like this: 执行此操作的标准方法是使用如下css规则:

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

If you inspect a validated input you will see all kinds of angular classes. 如果您检查已验证的输入,您将看到各种角度类。 One of them is .ng-touched. 其中一个是感动的。 A touched element is one that has been blurred at least once. 被触摸的元素是至少被模糊一次的元素。 If you wanted to ensure that validation is only applied on blur you could use ng-model-options directive. 如果您想确保仅对模糊应用验证,可以使用ng-model-options指令。

2) $formatters are used to format a model value. 2)$ formatters用于格式化模型值。 Angular has two way data binding. Angular有双向数据绑定。 That means that angular is $watching a model value and view value. 这意味着angular是$观看模型值和视图值。 If one of them changes angular executes a workflow to update the other one. 如果其中一个更改角度执行工作流程以更新另一个。 The workflows are as follows: 工作流程如下:

view value changes -> $parsers -> $validators -> update model value model value changes -> $formatters -> update view value 视图值更改 - > $ parsers - > $ validators - >更新模型值模型值更改 - > $ formatters - >更新视图值

The result of the work flow is populated into the other value. 工作流程的结果填充到另一个值中。 This means that if you want to change model value before showing it in the view (maybe you want to format a date) then you could do it in the $formatter. 这意味着如果您想在视图中显示模型值之前更改模型值(可能您想格式化日期),那么您可以在$ formatter中执行此操作。 Then, you could do the opposite operation in a $parser as it travels back to the model. 然后,您可以在$ parser中执行相反的操作,因为它返回到模型。 Of course, you should be cognizant of what is happening in the $parsers when you write your $validators because it is the parsed view value that gets validated before getting sent to the model. 当然,您应该在编写$ validators时了解$ parsers中发生的情况,因为它是在被发送到模型之前得到验证的已解析视图值。

3) Per the quote I added from the angular docs, it is clear that you should not use any logic that contains a value that has been bound to the controller by bindToController outside of $onInit. 3)根据我从angular docs添加的引用,很明显你不应该使用任何包含$ onInit之外的bindToController绑定到控制器的值的逻辑。 This includes ngModelCtrl. 这包括ngModelCtrl。 Note that you could place the logic in another function as long as you are sure that the other function will execute AFTER $onInit. 请注意,只要您确定其他函数将在$ onInit之后执行,您就可以将逻辑放在另一个函数中。

4) There are two things to consider here: Which control is having the error show up and where are you triggering the validation from. 4)这里要考虑两件事:哪个控件出现错误,以及从哪里触发验证。 It sounds like you want to trigger it from the dropdown's workflow (ie after it has been blurred once). 听起来你想从下拉列表的工作流程中触发它(即在它被模糊一次之后)。 So, I suggest adding a validator to the dropdown. 所以,我建议在下拉列表中添加验证器。 Now, you say you want to validate the input and not the dropdown. 现在,您说要验证输入而不是下拉列表。 So, you can use $setValidity inside the validator. 因此,您可以在验证器中使用$ setValidity。 To ensure that the dropdown is always "valid" you can just return true from the validator. 要确保下拉列表始终“有效”,您只需从验证程序返回true即可。 You say you want to only validate after blur. 你说你只想在模糊后验证。 There are two ways to do that (off the top of my head). 有两种方法可以做到这一点(在我的头顶)。 One is to use the ng-model-options that I mentioned above, the other is to test if the dropdown has been $touched in the validator. 一种是使用我上面提到的ng-model-options,另一种是测试验证器中是否触摸了下拉菜单。 Here is some code using the second method: 以下是使用第二种方法的一些代码:

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

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

You see, I am testing to see if the dropdown has been $touched (ie blurred) before I set the validity. 你看,在我设置有效性之前,我正在测试下拉是否被触及(即模糊)。 There is a fundemental difference between these two approaches. 这两种方法之间存在着根本的差异。 Using ng-model-options basically defers the whole update workflow until blur. 使用ng-model-options基本上会推迟整个更新工作流程直到模糊。 This means your model value will only get updated to match the view value after the input has been blurred. 这意味着您的模型值只会在输入模糊后更新以匹配视图值。 The second way (with $touched) will validate every time the viewValue changes but will only render the input invalid after the first blur. 第二种方式(使用$ touch)将在每次viewValue更改时进行验证,但仅在第一次模糊后呈现输入无效。

The 'validationName' argument will just specify the class that is added if the input is invalid so in this case it will add two classes .ng-invalid (added to any invalid control) and .ng-invalid-validation-name. 'validationName'参数将仅指定在输入无效时添加的类,因此在这种情况下,它将添加两个类.ng-invalid(添加到任何无效控件)和.ng-invalid-validation-name。

In order to get access to the formCtrl you need to add another property to your require object (formCtrl: '^form') 要访问formCtrl,您需要向require对象添加另一个属性(formCtrl:'^ form')

The easiest way to get access to the information provided by ngModel in a custom directive is to set the scope to false. 在自定义指令中访问ngModel提供的信息的最简单方法是将范围设置为false。 This should happen by default, but if you are working with multiple directive it can be helpful to set it expressly. 这应该是默认情况下发生的,但如果您正在使用多个指令,则明确设置它会很有帮助。 This way, the directive will inherit the controller and controller alias as if it were completely native to the rest of the view. 这样,该指令将继承控制器和控制器别名,就好像它对视图的其余部分完全是原生的一样。

The directive: 指令:

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

You don't have to change the template very much. 您不必非常更改模板。 Just make sure the ng-model="ctrl.name" is binding to something on your main controller, or whatever controller you are using for the rest of the view. 只需确保ng-model =“ctrl.name”绑定到主控制器上的某些内容,或者用于视图其余部分的任何控制器。 You can move the validation function to the main controller, too. 您也可以将验证功能移动到主控制器。 Or, to a service and inject into the controller, etc. 或者,到服务并注入控制器等。

Using compile or link in a custom directive can make it much more versatile. 在自定义指令中使用编译或链接可以使它更加通用。 But you are basically passing values for the directives, attributes, or html tags. 但是你基本上传递了指令,属性或html标签的值。 ngModel is available, but you may not be using ctrl.user every time you use the custom directive. ngModel可用,但每次使用自定义指令时都可能没有使用ctrl.user。 Compile or link let you set the value of ngModel each time you use the directive. 通过编译或链接,您可以在每次使用指令时设置ngModel的值。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM