Зачем нужен $timeout при динамическом вложении ng-формы в Angular, чтобы обеспечить связь дочерней формы с родительской формой?

Кажется, я не могу избежать необходимости создавать динамические подформы в приложении, над которым я работаю. Подформа работает, как и ожидалось, и подформа показывает $invalid=true, когда один или несколько ее входов недействительны. Однако родительская форма имеет $invalid=false.

Я видел, как люди достигают вложенных форм, в которых недопустимые подформы делают недействительной родительскую форму, но я не могу сделать это динамически, не заключая динамическую компиляцию подформы в $timeout.

См. плункер ЗДЕСЬ

В приведенной выше ссылке я воссоздал сценарий. У меня три формы. Родительская форма, подчиненная форма, созданная одновременно с родительской формой, и динамически создаваемая подчиненная форма.

Если вы очистите ввод нижней существующей подформы, это сделает родительскую форму недействительной (родительская форма станет красной).

Если вы очистите ввод верхней динамической формы, родительская форма не станет недействительной (родительская форма останется зеленой).

Он начнет работать, если вы вставите метод addForm в $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();

Здорово, что у меня есть обходной путь, но я хотел бы понять, зачем мне нужен тайм-аут $ и есть ли решение, которое позволяет избежать использования тайм-аута $.


person John David Five    schedule 19.06.2015    source источник
comment
отлично работает в моей системе .. какой браузер вы используете   -  person Parv Sharma    schedule 20.06.2015
comment
ого, правда? Mac 10.10.3, Chrome 43.0.2357.124   -  person John David Five    schedule 20.06.2015
comment
Это может не решить вашу проблему напрямую, поскольку этот вопрос объясняет преимущества использования $timeout . Это довольно распространенный взлом.   -  person Tony    schedule 20.06.2015
comment
Спасибо @Tony, это объясняет множество способов использования проблем с рендерингом, но я думаю, что это больше связано со значениями javascript в памяти.   -  person John David Five    schedule 20.06.2015


Ответы (3)


Как сказал Майкл, правильным местом для любых манипуляций с DOM является функция link, а не функция controller директивы. Некоторая дополнительная информация о том, почему то, что у вас уже есть, работает/не работает в зависимости от использования $timeout:

Согласно документации Angular $compile сервиса для определений директив контроллер

создается перед фазой предварительной компоновки

а функция link

выполняется после того, как шаблон был клонирован

Вы можете наблюдать это сами, если включите функцию link в свою директиву и напишите два оператора console.log, один в функции controller и один в функции link. Функция link всегда выполняется после функции controller. Теперь, когда вы включаете addForm(); в свой контроллер, это будет выполняться во время создания экземпляра controller, т.е. перед фазой связывания, когда, как указано в документации,

небезопасно выполнять преобразование DOM, поскольку функция связывания компилятора не сможет найти правильные элементы для связывания.

С другой стороны, если вы вызываете функцию addForm() в $timeout, это фактически будет выполнено после фазы компоновки, поскольку вызов $timeout с нулевым значением тайм-аута вызывает вызов кода в тайм-ауте в следующем цикле дайджеста, в этот момент связывание было выполнено и преобразование DOM выполнено правильно (еще раз вы можете увидеть время всех этих вызовов, добавив console.logs в соответствующих местах).

person Christina    schedule 23.06.2015

Манипуляции с DOM должны выполняться на этапе link, а не на этапе controller. См. раздел $compile.

Функция ссылки отвечает за регистрацию прослушивателей DOM, а также за обновление DOM. Он выполняется после того, как шаблон был клонирован. Именно здесь будет размещена большая часть директивной логики.

Детальное объяснение:

Проблема заключается в угловом 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;

Плункер

person Michael    schedule 23.06.2015

Обычно изменение элементов dom внутри контроллера обычно не идеально. Вы должны быть в состоянии достичь того, что ищете, без необходимости «$ compile» и немного упростить работу, если вы введете вторую директиву массив элементов для использования «ng-repeat». Я предполагаю, что $timeout() работает, чтобы сигнализировать angular о новых элементах и ​​заставляет цикл дайджеста обрабатывать правильную проверку.

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

person Mark Coleman    schedule 19.06.2015
comment
Да, этот подход работает, но не обязательно объясняет, почему я не могу динамически добавить подчиненную форму в контроллер во время ее первого прохода. В вашем примере, если я заполняю элементы начальным значением в контроллере, он также работает (не требует щелчка и другого дайджеста), поэтому, возможно, магия добавления динамической подформы заключается в ng-repeat? - person John David Five; 20.06.2015