从另一个指令编译指令

Compile directive from another directive

我目前正在将 ng-model-options 指令添加到我的一些输入框以进行去抖动。我的元素如下所示:

<input type="text" ng-model="search" ng-model-options="{ debounce: { 'default': 200 } }" />

我想把它放在一个指令中:

  1. 我的标记不那么繁琐。
  2. 我可以在一个地方控制去抖动值,以备不时之需。

我最终希望标记在使用 debounce 指令的地方看起来像这样:

<input type="text" ng-model="search" debounce />

我试过像这样实现这个指令:

app.directive('debounce', ['$compile', function ($compile) {
    return {
        restrict: 'A',
        replace: false,
        link: function (scope, element, attrs) {
            element.attr('ng-model-options', "{ debounce: { 'default': 200 } }");
            $compile(element.contents())(scope);
        }
    }
}]);

结果似乎是正确的 HTML,但去抖没有做任何事情。我的指令有什么问题?

Angular 不支持动态地向自己添加指令。您在编译方面走在正确的轨道上,但是 ng-model 指令已经在您执行此操作时进行了编译。

由于 ngModel 被记录为优先级 1 的 运行,您至少需要同时处于优先级 2 的 terminal 和 运行 以便您的 ng-model-options 在适当的时候获得链接。

app.directive('debounce', ['$compile', function ($compile) {
  return {
    restrict: 'A',
    priority: 2,
    terminal: true,
    compile: function(tElement) {
      tElement.attr('ng-model-options', "{ debounce: { 'default': 200 } }");

      return function (scope, element, attrs, controllers, transclude) {
        $compile(element, null, 2)(scope, {
          parentBoundTranscludeFn: transclude
        });
      };
    } 
  }
}]);

要执行相同的操作,您需要添加具有更高 priority 的指令以避免编译其他指令,这会将 terminal 选项设置为 true。这将表明当该指令为 运行 时没有其他指令,然后从该指令 remove 指令属性并添加 ng-model-options 以应用去抖动更改。

Removal of debounce attribute is necessary to avoid infinite compile

指令

app.directive('debounce', ['$compile', function ($compile) {
  return {
    restrict: 'A',
    priority: 1,
    terminal: true, 
    compile: function(element, attrs) {
      //compile when scope is not linked to the DOM.
      element.attr('ng-model-options', "{ debounce: { 'default': 200 } }");
      element.removeAttr('debounce'); //this removal is necessary to avoid infinite compile
      var compile = $compile(element);
      return function (scope, element, attrs) {
        var link = compile(scope);
      };
    } 
  }
}]);

实际上您甚至不需要访问该元素。您可以在 ngModel 控制器的 $options 属性 中设置选项并设置必要的值,如下所示:

ctrl.$options = {debounce:{default:300}, updateOnDefault: true};

代码:

.directive('debounce', ['$timeout',
  function($timeout) {
    return {
      restrict: 'A',
      require: 'ngModel',
      link: function(scope, element, attrs, ctrl) {
        var options = ctrl.$options || {};

        ctrl.$options = angular.extend(options, {
          debounce: {
            default: 300
          },
          updateOnDefault: true
        });

      }
    }
  }
]);

angular.module('app', []).directive('debounce', ['$timeout',
  function($timeout) {
    return {
      restrict: 'A',

      require: 'ngModel',
      replace: false,

      link: function(scope, element, attrs, ctrl) {
        var options = ctrl.$options || {};
        
        ctrl.$options = angular.extend(options || {}, {
          debounce: {
            default: 300
          },
          updateOnDefault: true
        });
        
       
      }
    }
  }
]).controller('test', function() {
  this.callMe = function() {
    console.log(this.search);
  }
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.0/angular.min.js"></script>
<div ng-app="app" ng-controller="test as vm">

  <input type="text" ng-model="vm.search" debounce ng-change="vm.callMe()" />
  <input type="text" ng-model="vm.search" ng-change="vm.callMe()" ng-model-options="{ debounce: { 'default': 200 } }" />{{vm.search}}
</div>

如果您想通过接受去抖动值作为属性来使其更具可配置性,那么:

.directive('debounce', ['$timeout',
  function($timeout) {
    return {
      restrict: 'A',
      require: 'ngModel',
      link: function(scope, element, attrs, ctrl) {
        var options = ctrl.$options || {updateOnDefault: true};

        ctrl.$options = angular.extend(options, {
          debounce: {
            default:  +attrs.debounce || 300
          }
        });

      }
    }
  }
])