0

モデルが汚れたり触れたりしたときに応答する入力要素の周りにディレクティブを構築しようとしています。必要な ngModel は、入力モデルの値とビューの変更を反映しているようですが、他の属性は反映されていません。

ng-model を 2 つの要素に含めているという事実と関係があるのではないかと疑っていますが、一度だけ使用する方法がわかりませんでした。

理想的には、次のように作成されたものが欲しいです。

<input test-directive label="'My Label'" type="text" ng-model="testObject.text"/>

結果は次のようになります。

<label>
    <div>My Label</div>
    <input ng-model="testObject.text" ng-blur="input.focus=false" ng-focus="input.focus=true"/>
    Focused: true (input.focus)
    Pristine: false (ngModel.$pristine)
</label>

これが私がこれまでに持っているものです:フィドル

<div test-directive ng-model="testObject.text" l="'Test Input'" f="testObject.focus">
    <input type="text" ng-model="testObject.text" ng-blur="testObject.focus=false" ng-focus="testObject.focus=true" />
</div>

ディレクティブは ngModel を監視します。

app.directive('testDirective', ['$compile',
    function ($compile) {
    'use strict';
    return {
        restrict: 'A',
    require: "ngModel",
    scope: {
        l: '=',
        f: '='
    },
    link: function (scope, element, attr, ngModel) {
        var input = element.find('input');
        scope.$watch(function () {
            return ngModel;
        }, function (modelView) {
            scope.modelView = modelView
        });
    },
    template:
        '<div>' +

        '<label>' +
        '{{l}}' +
        '<div class="iwc-input" ng-transclude></div>' +
        '</label>' +
        'focus: {{f}}' +
        '<pre>{{modelView|json}}</pre>' +
        '</div>',
    transclude: true,
    replace: false
    };

}]);
4

1 に答える 1

2

Angular では、他のディレクティブが適切に機能する一方で、ディレクティブを「自己ラップ」するのはかなり複雑であることがわかりました。したがって、以下の答えはうまくいきます。なぜそれが本来よりも複雑なのかを説明しようと思います.

これにアプローチする方法はいくつかあります。私はアプローチを使用しますtransclude: "element"- これは要素全体をトランスクルードし、どこにでも配置できるようにします (ラッピングを含む)。

.directive("wrapper", function($compile){
  return {
    scope: { l: "@" },
    transclude: "element",
    require: ["ngModel"],
    link: function(scope, element, attrs, ctrls, transclude)
      scope.ngModel = ctrls[0];

      // transclude: "element" ignores the template property, so compile it manually
      var template = '<label ng-class="{dirty: ngModel.$dirty}">{{l}}: \
                        <placeholder></placeholder>\
                      </label>';

      $compile(template)(scope, function(prelinkedTemplate){        
         transclude(function (clonedElement){
            prelinkedTemplate.find("placeholder").replaceWith(clonedElement);

            // element here is only a comment after transclusion
            // so, need to use .after() - not .append()
            element.after(prelinkedTemplate);
         });
      })
  }
})

したがって、上記はテンプレートをコンパイルし、isolate スコープ ($scope.l$scope.ngModelが利用可能な場合) に対してリンクし、要素を trasclude して<placeholder>.

これで十分なはずですが、問題があります。Angular がディレクティブをコンパイルしたとき、要素はトランスクルージョンされ、現在はコメント<!-- wrapper -->になっていますが、<input>これはngModelディレクティブが prelink 関数で「見る」ものであるため、問題が発生し始めます。

修正するには、ディレクティブの優先度をngModel(1) よりも高くする必要があります。実際には、うまく機能させるngAttributeDirectiveには (100)よりも優先度を高くする必要がありng-maxlengthます。しかし、それを行った場合 require: "ngModel"、優先度レベルではまだ利用できないため、単に .

これを修正する 1 つの方法は、優先度の高いパスと優先度の低いパスの 2 つのパスを作成することです。ngModel優先度の低いパスは、キャプチャされたコントローラーをディレクティブのコントローラーに「ハング」させます。方法は次のとおりです。

// first pass
app.directive("wrapper", function($compile) {
  return {
    priority: 101,
    scope: {
      l: "@"
    },
    transclude: "element",
    controller: angular.noop, // just a noop controller to attach properties to
    controllerAs: "ctrl", // expose controller properties as "ctrl"
    link: function(scope, element, attrs, ctrls, transclude) {

      // notice the change to "ctrl.ngModel"
      var template = '<label ng-class="{dirty: ctrl.ngModel.$dirty}">{{l}}: \
                        <placeholder></placeholder>\
                      </label>';

      $compile(template)(scope, function(prelinkedTemplate) {
        transclude(function(clonedElement) {
          prelinkedTemplate.find("placeholder").replaceWith(clonedElement);
          element.after(prelinkedTemplate);
        });
      });
    }
  };
})
// second pass
.directive("wrapper", function($compile) {
  return {
    priority: -1,
    require: ["wrapper", "ngModel"],
    link: function(scope, element, attrs, ctrls, transclude) {
      var wrapperCtrl = ctrls[0],
          ngModel = ctrls[1];

      // "hang" ngModel as a property of the controller
      wrapperCtrl.ngModel = ngModel;
    }
  };
});

デモ

他のアプローチもあります。たとえば、このディレクティブを非常に高い優先度 (たとえば、priority: 10000) とで作成することができますterminal: true。次に、要素を取得してラップし、実際に 、 などを追跡する必要がある別のディレクティブを適用して、require: "ngModel"コンテンツを再コンパイルします (無限ループを避けるために元のディレクティブを削除することを忘れないでください)。$pristine$touched

于 2015-07-29T20:57:43.603 に答える