簡體   English   中英

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

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

任何人都可以告訴我是否可以在自定義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>

這是重構的代碼(注意:你需要使用最新的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 0.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是$觀看模型值和視圖值。 如果其中一個更改角度執行工作流程以更新另一個。 工作流程如下:

視圖值更改 - > $ parsers - > $ validators - >更新模型值模型值更改 - > $ formatters - >更新視圖值

工作流程的結果填充到另一個值中。 這意味着如果您想在視圖中顯示模型值之前更改模型值(可能您想格式化日期),那么您可以在$ formatter中執行此操作。 然后,您可以在$ parser中執行相反的操作,因為它返回到模型。 當然,您應該在編寫$ validators時了解$ parsers中發生的情況,因為它是在被發送到模型之前得到驗證的已解析視圖值。

3)根據我從angular docs添加的引用,很明顯你不應該使用任何包含$ onInit之外的bindToController綁定到控制器的值的邏輯。 這包括ngModelCtrl。 請注意,只要您確定其他函數將在$ onInit之后執行,您就可以將邏輯放在另一個函數中。

4)這里要考慮兩件事:哪個控件出現錯誤,以及從哪里觸發驗證。 聽起來你想從下拉列表的工作流程中觸發它(即在它被模糊一次之后)。 所以,我建議在下拉列表中添加驗證器。 現在,您說要驗證輸入而不是下拉列表。 因此,您可以在驗證器中使用$ setValidity。 要確保下拉列表始終“有效”,您只需從驗證程序返回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更改時進行驗證,但僅在第一次模糊后呈現輸入無效。

'validationName'參數將僅指定在輸入無效時添加的類,因此在這種情況下,它將添加兩個類.ng-invalid(添加到任何無效控件)和.ng-invalid-validation-name。

要訪問formCtrl,您需要向require對象添加另一個屬性(formCtrl:'^ form')

在自定義指令中訪問ngModel提供的信息的最簡單方法是將范圍設置為false。 這應該是默認情況下發生的,但如果您正在使用多個指令,則明確設置它會很有幫助。 這樣,該指令將繼承控制器和控制器別名,就好像它對視圖的其余部分完全是原生的一樣。

指令:

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

您不必非常更改模板。 只需確保ng-model =“ctrl.name”綁定到主控制器上的某些內容,或者用於視圖其余部分的任何控制器。 您也可以將驗證功能移動到主控制器。 或者,到服務並注入控制器等。

在自定義指令中使用編譯或鏈接可以使它更加通用。 但是你基本上傳遞了指令,屬性或html標簽的值。 ngModel可用,但每次使用自定義指令時都可能沒有使用ctrl.user。 通過編譯或鏈接,您可以在每次使用指令時設置ngModel的值。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM