2

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

我们正在瞄准几个目标。

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

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

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

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

  • 虽然我可以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));
        }
    };
});
4

2 回答 2

2

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

在这里工作小提琴

父表格指令

首先,为父表单指令建立一个独立的范围,并my-model使用 2-way binding 导入属性。这可以通过指定来完成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 操作。它还保留了针对父范围编译和链接输入表单字段的原始意图,尽可能少地受到干扰。

于 2014-05-27T07:07:37.580 回答
1

事实证明这个问题之前已经被问过(并澄清过)here,但从未回答过。

这个问题也在AngularJS 邮件列表中被问到,这个问题得到了回答,尽管该解决方案导致一些臭代码。

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

.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控制器被实例化。这发生在任何预链接之前,这使得将myForm的变量暴露给myFormName指令成为可能。

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

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

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

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

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

于 2014-02-24T16:16:45.623 回答