2

アプリケーション全体で再利用される一般的に再利用されるフォーム入力のセットがあるため、それらをカスタム ディレクティブにカプセル化しようとしています。ディレクティブに を設定しngModel、メイン ディレクティブ内のいくつかの異なる入力 (そのうちのいくつかはディレクティブ自体) で編集できるように分割したいと考えています。

同時に、適切なメッセージとスタイルを表示できるように、フォームの検証結果をチェーンを介して親フォームに渡す必要があります。

これを実装する最も簡単で最も慣用的な方法は何ですか?

これらの (簡略化された) テンプレートは、私が何をしようとしているかの例を示しているはずです...

OuterTemplate.html

<form name="outerForm">
  <my-directive
    ng-model="ctrl.myComplexModel"
    name="myDirectiveInstance"
    custom-required="ctrl.EnableValidateOne"
    toggle-another-validation="ctrl.EnableValidateTwo">
  </my-directive>
  <div ng-messages="outerForm.myDirectiveInstance.$error">
    <ng-message when="customRequired">This is required.</ng-message>
    <ng-message when="anotherValidation">This is required.</ng-message>
    <ng-message when="innerValidationOne">Something wrong with field 1.</ng-message>
    <ng-message when="innerValidationTwo">Something wrong with field 2.</ng-message>
    <ng-message when="innerValidationThree">Something wrong with field 3.</ng-message>
    <!-- etc... -->
  </div>
</form>

myDirectiveTemplate.html

<div ng-form="myDirectiveForm">
  <div ng-class="{'has-error': myDirectiveForm.fieldOne.$invalid}">
    <ui-select
      ng-model="model.fieldOne"
      name="fieldOne"
      required>
    </ui-select>
  </div>
  <div ng-class="{'has-error': myDirectiveForm.fieldTwo.$invalid}">
    <input
      type="number"
      ng-model="model.fieldTwo"
      name="fieldTwo"
      ng-pattern="directiveCtrl.someRegEx"
      ng-required="directiveCtrl.fieldTwoIsRequired">
  </div>
  <!-- etc... -->
</div>

現時点では、myDirectiveFormとの両方myDirectiveInstanceが のプロパティとして公開されていますouterForm FormController。私はこのディレクティブをブラック ボックスにしたいと思っているので、myDirectiveForm直接アタッチしているという事実はouterForm私を悩ませ、私が何か間違ったことをしていることを示しているようです。

これが私のディレクティブ定義が今どのように見えるかです。

myDirective.js

app.directive('myDirective', function() {
  return {
    restrict: 'E',
    template: 'myDirectiveTemplate.html',
    controller: 'MyDirectiveCtrl',
    scope: {
      model: '=ngModel',
      customRequired: '=?',
      toggleAnotherValidation: '=?'
    },
    require: 'ngModel',
    link: function(scope, iElem, iAttrs, ngModelController) {

      // Black-box the internal validators

      // Custom validator to avoid conflicts with ngRequired
      ngModelController.$validators.customRequired = function(modelValue, viewValue) {
        if(!scope.customRequired)
          return true;

        // On first digest the field isn't registered on the form controller yet
        if(angular.isUndefined(scope.myDirectiveForm.fieldOne))
          return true;

        return !scope.myDirectiveForm.fieldOne.$error.required;
      };

      ngModelController.$validators.anotherValidation = function(modelValue, viewValue) {
        if(!scope.anotherValidation)
          return true;

        return scope.passesBusinessRule();
      };

      ngModelController.$validators.innerValidationOne = function(modelValue, viewValue) {
        if(!scope.anotherValidation)
          return true;

        if(angular.isUndefined(scope.myDirectiveForm.fieldTwo))
          return true;

        return !scope.myDirectiveForm.fieldTwo.$error.pattern;
      };

      /* etc... */

      // Deep-watching model so that validations will trigger on updates of properties
      scope.$watch('model', function() {
        ngModelController.$validate();
      }, true);
    }
  };
});
4

2 に答える 2

0

私はまともな解決策を考え出しました。簡単に言うと、カスタム ディレクティブから実装を削除し、カスタム ディレクティブ内のディレクティブNgModelControllerの内部に完全に依存しています。私が知る限り、カスタム ディレクティブでフォームをラップするようには設計されていません。ただし、ネストされたフォームは Angular で非常にうまくサポートされているため、これが進むべき道です。FormControllerformNgModelController

私が気付いていなかったのは、Angular 1.3 の時点で名前をフォームに動的に割り当てることができるということでした。「ブラック ボックス」がリークして親フォーム コントローラーにアタッチするのを防ぐことはできませんが、少なくとも親スコープでそれ自体を公開するために使用する名前を制御できます。これは受け入れられ、API に非常に似ています。によって提供されngModelます。

以下の例を更新しました。

OuterTemplate.html

<form name="outerForm">
  <my-directive
    model="ctrl.myComplexModel"
    name="myDirectiveInstance"
    custom-required="ctrl.EnableValidateOne"
    toggle-another-validation="ctrl.EnableValidateTwo">
  </my-directive>
  <div>
    <span ng-if="outerForm.myDirectiveInstance.fieldOne.$error.required">Internal field 1 is required.</span>
    <span ng-if="outerForm.myDirectiveInstance.fieldTwo.$error.required">Internal field 2 is required.</span>
    <span ng-if="outerForm.myDirectiveInstance.fieldTwo.$error.pattern">Internal field 2 format error.</span>
    <!-- etc... -->
    <ng-messages for="outerForm.myDirectiveInstance.$error">
      <ng-message when="required">At least one required field is missing.</ng-message>
      <ng-message when="custom">
        Some directive-wide error set by validate-custom on outerForm.myDirectiveInstance.internalField
      </ng-message>
      <!-- etc... -->
    </ng-messages>
  </div>
</form>

外側のテンプレートでng-modelは、カスタム属性を優先してディレクティブを削除しました。プロパティはname、内部フォームが公開されている名前を決定するために引き続き使用できます。

別の方法として、ng-modelを保持し、属性form-name(下記の isolate スコープ バインディングに適切な変更を加えたもの) を使用して、カスタム ディレクティブFormControllerを parentに発行することもできますが、ディレクティブは使用されていないFormControllerため、これは多少誤解を招く可能性があります。ng-modelアイソレート スコープ バインディングを除くすべて。

いずれにしても、このユース ケースのプロパティng-modelと組み合わせて使用​​しないでください。そうしないと、 とが同じプロパティ名 ( ) で親( )に自分自身を公開しようnameとして競合が発生する可能性があります。NgModelControllerFormControllerFormControllerouterFormouterForm.myDirectiveInstance

検証エラーは親formディレクティブにバブル アップするためngMessages、示されているように、このカスタム ディレクティブと共に使用できます。より詳細なエラー処理のために、ディレクティブの内部フィールドにもアクセスできます。

myDirectiveTemplate.html

<div ng-form="{{ formName }}">
  <div ng-class="{'has-error': isInvalid('fieldOne')}">
    <ui-select
      ng-model="model.fieldOne"
      name="fieldOne"
      required>
    </ui-select>
  </div>
  <div ng-class="{'has-error': isInvalid('fieldTwo')}">
    <input
      type="number"
      ng-model="model.fieldTwo"
      name="fieldTwo"
      ng-pattern="directiveCtrl.someRegEx"
      ng-required="directiveCtrl.fieldTwoIsRequired">
  </div>
  <!-- etc... -->
  <input
    type="hidden"
    ng-model="someCalculatedValue"
    name="internalField"
    validate-custom>
</div>

ディレクティブの内部テンプレートはほとんど同じままです。大きな違いは、 の名前ngFormが動的に設定されるようになったことです。

ngClass でそれを処理するには、角度式が機能しないため、$scope代わりに関数を使用するように例を更新しました。

最後に、ディレクティブ全体のビジネス ルールについては、ngModelディレクティブとnameセットで非表示の入力を使用しました。このフィールドだけに、検証用のカスタム ミニ ディレクティブを添付しました。このフィールドの検証エラーは、親ディレクティブによって使用されるためにバブルアップします。

myDirective.js

app.directive('myDirective', function() {
  return {
    restrict: 'E',
    template: 'myDirectiveTemplate.html',
    controller: 'MyDirectiveCtrl',
    scope: {
      model: '=',
      customRequired: '=?',
      toggleAnotherValidation: '=?',
      formName: '@name'
    },
  };
});

ほぼすべてのロジックがディレクティブ定義から削除されました。

于 2015-10-06T20:10:08.027 に答える