【问题标题】:AngularJS : Child input directive needs to compile in the scope of its parent for ng-model to bindAngularJS:子输入指令需要在其父级范围内编译,以便 ng-model 绑定
【发布时间】:2014-03-23 11:42:22
【问题描述】:

我们有一个在许多应用程序中使用的联系表。有许多重复的默认值、验证规则、结构等。我们正在研究一组指令,以使视图更加语义化且不那么冗长。

我们正在瞄准几个目标。

  1. 在父指令中定义一次联系表单模型,如下所示:<div my-form model='formModel'>。关联的子指令将能够从 model 属性中获取基本模型。

  2. 为每个输入提供默认配置(大小、验证规则、占位符、类等),但允许在必要时覆盖属性。因此,我们使用my-form 指令的控制器创建子指令进行通信。我们还希望这些子指令绑定到应用程序控制器的模型formModel

我在实现这一点时遇到了一些麻烦。

  • formModel 通过父指令的控制器公开,但我必须在 link 函数中使用 scope.$parent 手动 $compile 子指令。这对我来说似乎很臭,但是如果我尝试使用子指令的范围,编译的 HTML 包含正确的属性(它在源代码中可见),但它没有绑定到控制器并且它不会出现在任何范围内与 Batarang 一起检查。我猜我添加属性太晚了,但不确定如何更早添加属性。

  • 虽然我可以在每个子指令上使用ng-model,但这正是我想要避免的。我希望生成的视图非常干净,并且必须在每个字段上指定模型名称是重复且容易出错的。我还能如何解决这个问题?

这是一个jsfiddle,它具有我正在尝试完成的工作但“臭”的设置。

angular.module('myApp', []).controller('myCtrl', function ($scope) {
    $scope.formModel = {
        name: 'foo',
        email: 'foo@foobar.net'
    };
})
    .directive('myForm', function () {
    return {
        replace: true,
        transclude: true,
        scope: true,
        template: '<div ng-form novalidate><div ng-transclude></div></div>',
        controller: function ($scope, $element, $attrs) {
            $scope.model = $attrs.myModel;
            this.getModel = function () {
                return $scope.model;
            };
        }
    };
})
    .directive('myFormName', function ($compile) {
    return {
        require: '^myForm',
        replace: true,
        link: function (scope, element, attrs, parentCtrl) {

            var modelName = [parentCtrl.getModel(),attrs.id].join('.'),
                template = '<input ng-model="' + modelName + '">';

            element.replaceWith($compile(template)(scope.$parent));
        }
    };
});

【问题讨论】:

    标签: angularjs angularjs-directive transclusion


    【解决方案1】:

    有一个更简单的解决方案。

    Working Fiddle Here

    父表单指令

    首先,为父表单指令建立一个独立的范围,并使用 2-way binding 导入 my-model 属性。这可以通过指定scope: { model:'=myModel'} 来完成。确实没有必要指定原型范围继承,因为您的指令没有使用它。

    您的隔离作用域现在已经导入了“模型”绑定,我们可以使用这个事实来编译和链接子指令针对父作用域。为此,我们将从父指令公开一个compile 函数,子指令可以调用该函数。

    .directive('myForm', function ($compile) {
    return {
        replace: true,
        transclude: true,
        scope: { model:'=myModel'},
        template: '<div ng-form novalidate><div ng-transclude></div></div>',
        controller: function ($scope, $element, $attrs) {
            this.compile = function (element) {
                $compile(element)($scope);
            };
        }
    }; 
    

    子字段指令

    现在是时候设置您的子指令了。在指令定义中,使用require:'^myForm' 指定它必须始终驻留在父表单指令中。在您的编译函数中,添加ng-model="model.{id attribute}"。无需弄清楚模型的名称,因为我们已经知道“模型”将在父范围内解析为什么。最后,在您的链接函数中,只需调用您之前设置的父控制器的编译函数。

    .directive('myFormName', function () {
    return {
        require: '^myForm',
        scope: false,
        compile: function (element, attrs) {
            element.attr('ng-model', 'model.' + attrs.id);
            return function(scope, element, attrs, parentCtrl) {
                parentCtrl.compile(element);
    
            };
          }
       };
    });
    

    这个解决方案是最小的,只需要很少的 DOM 操作。它还保留了针对父范围编译和链接输入表单字段的初衷,尽可能少地受到干扰。

    【讨论】:

    • 小提琴演示错误Error: Maximum call stack size exceeded
    【解决方案2】:

    原来这个问题在here 之前已经被问过(并已澄清),但从未得到回答。

    AngularJS mailing list 上也提出了这个问题,该问题得到了回答,尽管该解决方案导致一些臭代码。

    以下是 Daniel Tabuenca 在 AngularJS 邮件列表中的回复,为了解决这个问题,我们做了一些改动。

    .directive('foo', function($compile) {
    
      return {
        restrict: 'A',
        priority: 9999,
        terminal: true, //Pause Compilation to give us the opportunity to add our directives
        link: function postLink (scope, el, attr, parentCtrl) {
            // parentCtrl.getModel() returns the base model name in the parent
            var model = [parentCtrl.getModel(), attr.id].join('.');
            attr.$set('ngModel', model);
            // Resume the compilation phase after setting ngModel
            $compile(el, null /* transclude function */, 9999 /* maxPriority */)(scope);
        }
      };
    });
    

    解释:

    首先,myForm 控制器被实例化。 This happens before any pre-linking,这使得将myForm 的变量暴露给myFormName 指令成为可能。

    接下来,myFormName 设置为最高优先级 (9999),terminal 的属性设置为 true。 devdocs 说:

    如果设置为 true,则当前优先级将是最后一组将执行的指令(当前优先级的任何指令仍将执行,因为相同优先级的执行顺序未定义)。

    通过以相同的优先级 (9999) 再次调用 $compile,我们会为任何较低优先级的指令恢复指令编译。

    $compile 的这种使用似乎没有记录,因此使用风险自负。

    我真的想要一个更好的模式来解决这个问题。请让我知道是否有更易于维护的方法来实现此最终结果。谢谢!

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-11-06
      • 1970-01-01
      • 2014-10-25
      • 1970-01-01
      • 2021-01-19
      • 2014-08-07
      • 1970-01-01
      相关资源
      最近更新 更多