AngularJS: дочерняя директива ввода должна быть скомпилирована в рамках своего родителя для привязки ng-модели.

У нас есть контактная форма, которую мы используем во многих приложениях. Есть много значений по умолчанию, правил проверки, структуры и т. д., которые повторяются. Мы работаем над набором директив, чтобы сделать представление более семантическим и менее подробным.

Есть несколько целей, по которым мы стреляем.

  1. Однократное определение модели контактной формы в родительской директиве следующим образом: <div my-form model='formModel'>. Связанные дочерние директивы смогут получить базовую модель из атрибута model.

  2. Укажите конфигурацию по умолчанию (размер, правила проверки, заполнители, классы и т. д.) для каждого входа, но при необходимости разрешите перезапись атрибутов. Таким образом, мы создаем дочерние директивы, используя для связи контроллер директивы my-form. Мы также хотим, чтобы эти дочерние директивы были связаны с моделью контроллера приложения formModel.

У меня возникли проблемы с реализацией этого.

  • formModel отображается через контроллер родительской директивы, но мне приходится вручную $compile дочернюю директиву использовать scope.$parent в функции link. Мне это кажется вонючим, но если я попытаюсь использовать область действия дочерней директивы, скомпилированный HTML-код содержит правильный атрибут (он виден в исходном коде), но он не привязан к контроллеру и не отображается ни в какой области, когда осмотрен с помощью Бэтаранга. Я предполагаю, что добавляю атрибут слишком поздно, но не знаю, как добавить атрибут раньше.

  • Хотя я мог бы просто использовать ng-model для каждой дочерней директивы, это именно то, чего я пытаюсь избежать. Я хочу, чтобы результирующее представление было очень чистым, а необходимость указывать имена моделей в каждом поле повторялась и приводила к ошибкам. Как еще я могу это решить?

Вот jsfiddle с работающей, но "вонючей" настройкой того, что я пытаюсь сделать.

angular.module('myApp', []).controller('myCtrl', function ($scope) {
    $scope.formModel = {
        name: 'foo',
        email: '[email protected]'
    };
})
    .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));
        }
    };
});

person corinna000    schedule 21.02.2014    source источник


Ответы (2)


Есть гораздо более простое решение.

Здесь работает скрипт

Директива родительской формы

Сначала установите изолированную область для директивы родительской формы и импортируйте атрибут my-model с двусторонней привязкой. Это можно сделать, указав 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. Кроме того, он сохраняет первоначальную цель компиляции и связывания полей формы ввода с родительской областью с минимальным вмешательством.

person pixelbits    schedule 27.05.2014
comment
Демонстрация скрипки получает ошибку Error: Maximum call stack size exceeded - person Codler; 26.03.2015

Оказывается, этот вопрос уже задавали (и уточняли) attribute-on-directive#comment29057393_19578840">здесь, но так и не ответил.

Вопрос также был задан на AngularJS. список рассылки, где БЫЛ ответ на вопрос, хотя решение приводит к некоторому вонючему коду.

Ниже приведен ответ Даниэля Табуэнки из списка рассылки AngularJS, который немного изменился, чтобы решить этот вопрос.

.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. Девдок говорит:

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

Повторным вызовом $compile с тем же приоритетом (9999) мы возобновим компиляцию директивы для любой директивы с более низким уровнем приоритета.

Такое использование $compile не задокументировано, поэтому используйте его на свой страх и риск.

Мне бы очень хотелось, чтобы для этой проблемы был более приятный шаблон. Пожалуйста, дайте мне знать, если есть более удобный способ достижения этого конечного результата. Спасибо!

person corinna000    schedule 24.02.2014