观察嵌入输入中 $pristine 或 $touched 的变化

Watching for changes in $pristine or $touched in transcluded input

我正在尝试围绕一个输入元素构建一个指令,该指令在模型变脏或被触摸时做出响应。所需的ngModel似乎反映了输入模型的值和视图的变化,但其他属性的none。

我怀疑这与我将 ng-model 包含在两个元素中有关,但我还没有想出如何使用它一次。

理想情况下,我想要这样创建的东西:

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

这是我目前的情况:fiddle

<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
    };

}]);

我发现在 Angular 中有一个指令 "self-wrap" 是相当复杂的,同时仍然有其他指令与它一起正常工作。所以,下面的答案有效,我将尝试解释为什么它比应该的更复杂。

有多种方法可以解决这个问题。我将使用带有 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);
         });
      })
  }
})

因此,上面的代码编译了模板并链接到隔离范围(其中 $scope.l$scope.ngModel 可用),然后排斥元素并替换 <placeholder>

这应该已经足够了,但是有一个问题。当 Angular 编译我们的指令时,该元素已被嵌入并且现在是注释 <!-- wrapper -->,而不是 <input> - 这就是 ngModel 指令 "sees" 在其预链接功能,所以事情开始中断。

要修复,我们的指令需要比 ngModel(即 1)具有更高的优先级,事实上,对于诸如 ng-maxlength 去上班。但如果我们这样做,那么我们就不能 require: "ngModel",因为它在我们的优先级还不可用。

解决此问题的一种方法是进行 2 次传递 - 一次优先级较高,一次优先级较低。优先级较低的传递会将 "hang" 捕获的 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;
    }
  };
});

Demo

还有其他方法。例如,我们可以使该指令具有非常高的优先级(例如 priority: 10000)和 terminal: true。然后,我们可以获取元素,将其包装,应用另一个具有 require: "ngModel" 的指令来实际跟踪 $pristine$touched 等...并重新编译内容(不要忘记删除原始指令以避免无限循环)。