且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

使用内插名称发出注册表单控件

更新时间:2023-10-02 09:06:52

发生这种情况是因为在 ngModelController 的实例化,根据文档 发生在预链接阶段*之前(因此还没有插值).

This happens because the control's name (the one with which it is registered on its parent form) is retrieved during the ngModelController's instantiation, which according to the docs takes place before the pre-linking phase* (so no interpolation yet).

如果您检查 myForm 的属性,您会发现它确实有一个键为itemName{{$index}}"的属性.

If you inspect the myForm's properties, you will find out it indeed has a property with key "itemName{{$index}}".

*更新

$compile上的文档a> 是一个很好的资源,可以帮助您了解指令的内容以及幕后"发生的事情.

The docs on $compile are a great resource for understanding what makes a directive tick and what is going on "behind the scenes".

简单来说,有两个主要阶段:编译阶段和链接阶段.

In plain words there are two main phases: the compiling phase and the linking phase.

编译阶段,模板正在准备中(例如,它可能需要被克隆等)并使其具有 Angular 感知能力(例如编译指令和解析表达式并准备好被评估),但它还没有绑定到一个范围(因此没有什么可以评估的).

During the compiling phase, the template is being prepared (e.g. it might need to be cloned etc) and is made Angular-aware (e.g. the directives are compiled and the expressions are parsed and ready to be evaluated), but it is not bound to a scope yet (thus there is nothing to evaluate against).

compile 函数处理模板 DOM 的转换.由于大多数指令不进行模板转换,因此不经常使用.需要编译函数的示例是转换模板 DOM 的指令,例如 ngRepeat,或异步加载内容的指令,例如 ngView.

The compile function deals with transforming the template DOM. Since most directives do not do template transformation, it is not used often. Examples that require compile functions are directives that transform template DOM, such as ngRepeat, or load the contents asynchronously, such as ngView.

链接阶段进一步分为两个子阶段:链接前链接后.

The linking phase is further devided into two sub-phases: pre-linking and post-linking.

在此阶段,作用域发挥作用,可以根据作用域的属性/函数评估内插表达式(例如您的 name 属性).

During this phase, a scope comes into play and the interpolated expressions (such as your name attribute) can be evaluated against the scope's properties/functions.

link 函数负责注册 DOM 监听器以及更新 DOM.它在模板被克隆后执行.这是大部分指令逻辑将被放置的地方.

The link function is responsible for registering DOM listeners as well as updating the DOM. It is executed after the template has been cloned. This is where most of the directive logic will be put.

预链接功能
在链接子元素之前执行.进行 DOM 转换是不安全的,因为编译器链接函数将无法找到正确的链接元素.

Pre-linking function
Executed before the child elements are linked. Not safe to do DOM transformation since the compiler linking function will fail to locate the correct elements for linking.

后链接功能在子元素链接后执行.在 post-linking 函数中做 DOM 转换是安全的.

Post-linking function Executed after the child elements are linked. It is safe to do DOM transformation in the post-linking function.

因此,就您而言,会发生以下情况:


So, in your case, here is what happens:

  1. ngModel 指令负责在其父表单的 FormController 上注册元素,调用 formCtrl.$addControl(modelCtrl); 在其后链接功能中.

  1. The ngModel directive, which is responsible for registering the element on its parent form's FormController, calls formCtrl.$addControl(modelCtrl); in its post-linking function.

FormController 使用指定控制器的 $name 属性来注册控件:
form[control.$name] = control;

The FormController uses the specified controller's $name property to register the control:
form[control.$name] = control;

ngModel 的情况下,控制器是 ngModelCntroller 的一个实例,它的 $name 属性定义如下:
function(..., $attr, ...) { ... this.$name = $attr.name;

In the case of ngModel the controller is an instance of ngModelCntroller and it's $name property is defined like this:
function(..., $attr, ...) { ... this.$name = $attr.name;

由于控制器在预链接阶段之前被实例化,$attr.name 绑定到未插入的字符串(即itemName{{$index}}").

Since the controller is instantiated before the pre-linking phase, $attr.name is bound to the un-interpolated string (i.e. "itemName{{$index}}").

更新 2

既然我们知道问题是什么,继续解决它似乎是合乎逻辑的:)

Now that we know what the issue is, it only seems logical to go ahead and fix it :)

这是一个可以解决问题的实现:

Here is an implementation that would solve the issue:

  1. 不要设置name属性,所以myForm不会注册任何东西(我们会手动注册).

  1. Do not set a name attribute, so nothing is registered with myForm (we will take care of the registering manually).

创建一个指令,仅在针对元素的范围评估表达式后,才将控件注册到父表单的 FormController(让我们调用指令 later-name).

Create a directive that registers the control with the parent form's FormController only after evaluating the expression against the element's scope (let's call the directive later-name).

由于控件通过它们的 ngModelController 注册到 FormController,我们的指令必须访问这两个控制器(通过它的 require 属性).

Since the controls are registered to the FormController through their ngModelController, our directive must get access to those two controllers (through its require property).

在注册控件之前,我们的指令将更新 ngModelController$name 属性(并在元素上设置一个名称).

Before registering the control, our directive will update the ngModelController's $name property (and set a name on the element).

我们的指令还必须注意手动"删除控件(因为我们是手动添加的).

Our directive must also take care of removing the control "manually" (since we are adding it manually).

简单易行:

<select later-name="itemName{{$index}}"                  <!-- (1) -->

app.directive('laterName', function () {                   // (2)
    return {
        restrict: 'A',
        require: ['?ngModel', '^?form'],                   // (3)
        link: function postLink(scope, elem, attrs, ctrls) {
            attrs.$set('name', attrs.laterName);

            var modelCtrl = ctrls[0];                      // (3)
            var formCtrl  = ctrls[1];                      // (3)
            if (modelCtrl && formCtrl) {
                modelCtrl.$name = attrs.name;              // (4)
                formCtrl.$addControl(modelCtrl);           // (2)
                scope.$on('$destroy', function () {
                    formCtrl.$removeControl(modelCtrl);    // (5)
                });
            }            
        }
    };
});

另请参阅此简短演示.

See, also, this short demo.