1

I cannot seem to avoid the need to generate dynamic sub forms in the application I am working on. The sub form is working as expected and the sub form shows $invalid=true when one or more of it's inputs are invalid. The parent form however has $invalid=false.

I have seen people achieve nested forms where invalid sub forms invalidate the parent form, but I can't seem to do it dynamically without wrapping the dynamic compiling of the sub form in a $timeout.

See Plunker HERE

In the above link I have recreated the scenario. I have three forms. The parent form, a sub form created at the same time as the parent form, and a dynamically created sub form.

If you clear the bottom existing sub form's input, it will invalidate the parent form (parent form turns red).

If you clear the top dynamic form's input, it will not invalidate the parent form (parent form remains green).

It will begin to work if you stick the addForm method in a $timeout:

// WORKS! : When you delete the dynamic added sub form input
// the parent form also becomes invalid
//timeout(addForm,0); 

// FAILS! : When you delete the dynamic added sub form input text
// the parent form does NOT become invalid
addForm();

It's great that I have a workaround, but I would like to understand why I need the $timeout and if there is a solution that avoids the use of a $timeout.

4

3 回答 3

2

DOM 操作应该在link阶段完成,而不是在controller. 见$compile

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

详细解释:

问题在于 angular FormController。在初始化时,它将寻找父form控制器实例。由于子表单是在控制器阶段创建的 - 父表单初始化尚未完成。子表单找不到它的父控制器,也无法将自己注册为子控件元素。

FromController.js

//asks for $scope to fool the BC controller module
FormController.$inject = ['$element', '$attrs', '$scope', '$animate', '$interpolate'];
function FormController(element, attrs, $scope, $animate, $interpolate) {
  var form = this,
      controls = [];

  var parentForm = form.$$parentForm = element.parent().controller('form') || nullFormCtrl;

普朗克

于 2015-06-23T08:24:53.710 回答
2

正如 Michael 所说,进行任何 DOM 操作的正确位置是link函数,而不是controller指令的函数。一些额外的信息,说明为什么你已经拥有的东西根据使用情况而起作用/不起作用$timeout

根据指令定义服务的 Angular 文档,$compile控制器

在预链接阶段之前实例化

link函数

在模板被克隆后执行

link如果您在指令中包含一个函数并编写两条console.log语句,一条在controller函数中,一条在函数中,您可以自己观察这一点link。该link函数总是在controller. 现在,当您包含addForm();在控制器中时,它将在controller实例化时执行,即。在链接阶段之前,此时,正如文档中提到的那样

进行 DOM 转换不安全,因为编译器链接功能将无法找到正确的链接元素。

另一方面,如果您addForm()在 $timeout 中调用该函数,这实际上将在链接阶段之后执行,因为$timeout具有零超时值的调用会导致在下一个摘要循环中调用超时中的代码,此时指向已执行链接并且正确执行了 DOM 转换(您可以再次通过console.logs在适当的位置添加来查看所有这些调用的时间)。

于 2015-06-23T11:00:05.837 回答
1

通常更改控制器内部的 dom 元素通常并不理想。如果您引入第二个指令一组项目以使用“ng-repeat”,您应该能够在不需要“$compile”的情况下实现您正在寻找的东西,并且使事情更容易处理。我的猜测是,它$timeout()正在努力就新元素发出角度信号,并导致摘要循环处理正确的验证。

var app = angular.module('app',[]);
app.directive('childForm', function(){
  return{
    restrict: 'E"',
    scope:{
      name:"="
    },
    template:[
                '<div ng-form name="form2">',
                    '<div>Dynamically added sub form</div>',
                    '<input type="text" name="input1" required ng-model="name"/>',
                    '<button ng-click="name=\'\'">CLEAR</button>',
                '</div>'
              ].join('')

  }
});
app.directive('myTest', function() {

    return {

        restrict: 'E',

        scope: {},

        controller: function ($scope, $element, $compile, $timeout) {
          $scope.items = [];
          $scope.items.push({
            name:'myname'
          });
          $scope.name = 'test';
            $scope.onClick = function () {
                console.log("SCOPE:", $scope, $childScope);
            };
            $scope.addItem = function(){
              $scope.items.push({name:''});
            }
        },

        template: [
            '<div>',
              '<div>Parent Form</div>',
              '<div ng-form name="form1">',

                  '<div class="form-container">',
                    '<div ng-repeat="item in items">',
                      '<child-form/ name="item.name">',
                    '</div>',
                  '</div>',

                  '<div>Existing sub form on parent scope</div>',
                  '<div ng-form name="form3">',
                    '<input type="text" name="input2" required ng-model="name"/>',
                    '<button ng-click="name=\'\'">CLEAR</button>',
                  '</div>',
              '</div>',
              '<button ng-click="addItem()">Add Form</button>',
              '<button ng-click="onClick()">Console Out Scopes</button>',
            '</div>'
        ].join('')
    };
});

更新了 plunkr

于 2015-06-19T22:43:55.570 回答