手动应用ngModel指令

时间:2014-07-16 16:32:54

标签: javascript angularjs angularjs-directive angular-ngmodel

我的指令需要使用ngModel

我需要在另一个指令中动态执行此操作,因为我想用范围做一些时髦的东西,并从编写HTML的人那里抽象出来。

我的第一个想法是使用$set函数中attrs参数提供的link函数,该函数用于修改HTML,但指令本身不会被编译。然后,我们可以将其与$compile提供程序结合使用,并且可以正常运行。

attrs.$set('ngModel', someVar);
$compile(element)(scope);

问题在于,如果我不能(并且我不能)替换元素标记,则会创建无限递归,因为指令会被无限期地重新应用和重新编译。

然而,我可以摆弄优先事项并让它发挥作用:

module.directive('input', [
  '$compile',
  function($compile) {
    return {
      restrict: 'E',
      scope: {},
      priority: 100, // Set this high enough to perform other directives
      terminal: true, // Make sure this is the last directive parsed
      link: function(scope, element, attrs) {
          var key = 'example';
          attrs.$set('ngModel', key);
          $compile(element, null, 100)(scope);
      }
    };
  }
]);

这很好用,但感觉不对:

  • 我现在必须确保元素上的所有其他指令都是 能够被重新编译,因为它们都会被编译两次。

  • 我必须确保没有人使用更高的优先级。

所以这让我想到为什么我不能只注入ngModelDirective并强制对我的元素进行编译?

module.directive('input', [
  'ngModelDirective',
  function(ngModel) {
    return {
      restrict: 'E',
      scope: {},
      priority: 100, // Set this high enough to perform other directives
      terminal: true, // Make sure this is the last directive parsed
      require: '?^form',
      link: function(scope, element, attrs, formCtrl) {
          var key = 'example';
          attrs.$set('ngModel', key);
          var ngModelFactory = ngModel[0];
          var ngModelLink = ngModelFactory.compile(element);
          ngModelLink.call(this, scope, element, attrs, [ngModelFactory.controller, formCtrl]);
      }
    };
  }
]);

请参阅:https://github.com/angular/angular.js/blob/v1.2.x/src/ng/directive/input.js#L1356

没有错误,但没有任何反应。这似乎还不足以将其连接起来,所以我的问题是,任何人都可以详细说明我需要做什么将ngModelDirective链接到我的自定义指令而不强制重新编译?

3 个答案:

答案 0 :(得分:1)

如果没有重新编译,我认为这是不可能的。

ngModel被设计为同一元素中的其他指令之间的一种协作者,也是父形式指示符。例如,在complilation期间:

  • 其他指令(例如输入,必需或ng-change)可以将自己的$parser$formatter添加到ngModel。
  • ngModel会将自身添加到父表单指令(如果存在)。

因此,如果在复杂化过程结束后以某种方式添加ngModel,则上述两个操作将会丢失。

编辑:如果要分配给ng-model属性的值在编译时已知,则可能会出现如下情况:

app.directive('myNgModel', function($compile) {
  return {
    restrict: 'A',
    replace: false,
    priority: 1000,
    terminal: true, // these terminal and priority will stop all other directive from being compiled.
    link: function (scope, element, attrs) {
      var key = 'example';

      attrs.$set('ngModel', key);
      attrs.$set('myNgModel', null); // remove itself to avoid a recusion

      $compile(element)(scope); // start compiling other directives
    }
  };
});

以下是带有示例的plunker:http://plnkr.co/edit/S2ZkiVIyq2bOK04vAnFO?p=preview

答案 1 :(得分:1)

ngModel似乎不适合你要做的事情。但无论如何你根本不需要它。您可以双向绑定某个变量并将名称传递给模型指令范围:

app.directive("myDirective", function() {
    // ...
    scope: {
        myModel = "=",
        modelName = "myModel"
        // ...
    }
    // ...
});

app.directive("ngModelDirective", function() {
    // ...
    // ...
    transclude: true,
    link: function(scope, element, attrs) {
        var modelName = scope.modelName;
        console.assert(modelName, '`modelName` must be set when using `ngModelDirective`.');
        // TODO: Check if `scope[modelName]` is actually bound
        doSomethingFancyWith(scope, modelName);
    }
});

模板示例:

<myDirective ngModelDirective my-model="..." />

请注意doSomethingFancyWith可以读取和写入模型变量,并绑定到外部世界。

答案 2 :(得分:0)

我设法做到了。它不是最漂亮的东西,但它可以工作,我可以使用本地input连接我的inputDirective指令,以便它可以使用require之类的东西或验证特定的输入类型。

要针对另一个实现特定ngModel功能的标准指令(例如ngChange)进行构建,只需将注入的inputDirective替换为正确的指令,例如ngChangeDirective

module.directive('input', function() {
  return {
    restrict: 'E',
    scope: {},
    require: '?ngModel',
    priority: -1,
    link: function(scope, element, attrs, ngModel) {
      var key = 'example.property';

      if (ngModel === undefined) {
        attrs.$set('ngModel', key);
        angular.injector(['ng']).invoke([
          'inputDirective',
          'ngModelDirective',
          '$controller',
          '$exceptionHandler',
          '$parse',
          '$animate',
          function(inputDirective, ngModelDirective, $controller, $exceptionHandler, $parse, $animate) {
            var ngModelFactory = ngModelDirective[0];
            var ngModelLink = ngModelFactory.compile(scope); // Get the ngModel linkage function against this scope
            ngModel = $controller(ngModelFactory.controller, {
              $scope: scope,
              $exceptionHandler: $exceptionHandler,
              $attrs: attrs,
              $element: element,
              $parse: $parse,
              $animate: $animate
            }); // Call the ngModel controller and bootstrap it's arguments
            // Call the inputDirective linkage function to set up the ngModel against this input
            inputDirective[0].link(scope, element, attrs, ngModel);
            element.data('$ngModelController', ngModel); // Allow additional directives to require ngModel on this element.
          }
        ]);
      }
    }
  };
});

注意:这不适用于ngOptions,因为它指定了terminal: true