Why is $timeout needed when dynamically nesting ng-form in Angular to ensure child form links with parent form?

假如想象 提交于 2019-12-07 06:31:25

As Michael said, the correct place to do any DOM manipulations is the link function and not the controller function of the directive. Some extra information on why what you already have does / does not work depending on the use of $timeout:

According to the Angular documentation of the $compile service for directive definitions the controller

is instantiated before the pre-linking phase

while the link function

is executed after the template has been cloned

You can observe this yourself if you include a link function in your directive and write two console.log statements, one in the controller function and one in the link function. The link function is always executed after the controller. Now, when you include addForm(); in your controller this will be executed at the time the controller is instantiated, ie. before the linking phase, at which time, as it is mentioned in the documentation it is

not safe to do DOM transformation since the compiler linking function will fail to locate the correct elements for linking.

On the other hand, if you call the addForm() function in the $timeout, this will actually be executed after the linking phase, since a $timeout call with a zero timeout value causes the code in the timeout to be called in the next digest cycle, at which point the linking has been performed and the DOM transformation is performed correctly (once again you can see the timing of all these calls by adding console.logs in appropriate places).

DOM manipulations should be done in the link phase and not in the controller. See $compile

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.

Detailed explanation:

The problem lies in the the angular FormController. At intialization it will look for a parent form controller instance. As the sub form was created in the controller phase - the parent form initialization has not been finished. The sub form won't find it's parent controller and can't register itself as a sub control element.

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;

Plunker

Usually altering dom elements inside of a controller is typically not ideal. You should be able achieve what you are looking for without the need for '$compile' and make things a bit easier to handle if you introduce a second directive an array of items to use 'ng-repeat'. My guess is that $timeout() is working to signal angular about the new elements and causes a digest cycle to handle the correct validation.

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('')
    };
});

Updated plunkr

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!