Налаштування шаблону в рамках Директиви


98

У мене є форма, яка використовує розмітку з Bootstrap, наприклад наступну:

<form class="form-horizontal">
  <fieldset>
    <legend>Legend text</legend>
    <div class="control-group">
      <label class="control-label" for="nameInput">Name</label>
      <div class="controls">
        <input type="text" class="input-xlarge" id="nameInput">
        <p class="help-block">Supporting help text</p>
      </div>
    </div>
  </fieldset>
</form>

Там є багато кодових кодів, які я хотів би звести до нової директиви - форма-введення, наприклад:

<form-input label="Name" form-id="nameInput"></form-input>

генерує:

   <div class="control-group">
      <label class="control-label" for="nameInput">Name</label>
      <div class="controls">
        <input type="text" class="input-xlarge" id="nameInput">
      </div>
    </div>

У мене це багато працює за допомогою простого шаблону.

angular.module('formComponents', [])
    .directive('formInput', function() {
        return {
            restrict: 'E',
            scope: {
                label: 'bind',
                formId: 'bind'
            },
            template:   '<div class="control-group">' +
                            '<label class="control-label" for="{{formId}}">{{label}}</label>' +
                            '<div class="controls">' +
                                '<input type="text" class="input-xlarge" id="{{formId}}" name="{{formId}}">' +
                            '</div>' +
                        '</div>'

        }
    })

Однак коли я приїжджаю додати вдосконалену функціональність, я зациклююся.

Як я можу підтримати значення за замовчуванням у шаблоні?

Я б хотів виставити параметр "type" як необов'язковий атрибут у моїй директиві, наприклад:

<form-input label="Password" form-id="password" type="password"/></form-input>
<form-input label="Email address" form-id="emailAddress" type="email" /></form-input>

Однак якщо нічого не вказано, я б хотів зробити це за замовчуванням "text". Як я можу це підтримати?

Як я можу налаштувати шаблон на основі наявності / відсутності атрибутів?

Я також хотів би мати можливість підтримувати "обов'язковий" атрибут, якщо він присутній. Наприклад:

<form-input label="Email address" form-id="emailAddress" type="email" required/></form-input>

Якщо requiredв директиві присутній, я хотів би додати її до згенерованого <input />у висновку та ігнорувати його інакше. Я не впевнений, як цього досягти.

Я підозрюю, що ці вимоги, можливо, вийшли за рамки простого шаблону, і мені доведеться почати використовувати фази попереднього компіляції, але я втрачаю, з чого почати.


Я єдиний, хто бачить слона в кімнаті :) -> Що робити, якщо typeце встановлено динамічно через прив'язку, наприклад. type="{{ $ctrl.myForm.myField.type}}"? Я перевірив усі методи, наведені нижче, і не зміг знайти жодного рішення, яке діятиме в цьому сценарії. Схоже, що шаблонна функція побачить буквальні значення атрибутів, наприклад. tAttr['type'] == '{{ $ctrl.myForm.myField.type }}'замість tAttr['type'] == 'password'. Я спантеличений.
Димитрій К

Відповіді:


211
angular.module('formComponents', [])
  .directive('formInput', function() {
    return {
        restrict: 'E',
        compile: function(element, attrs) {
            var type = attrs.type || 'text';
            var required = attrs.hasOwnProperty('required') ? "required='required'" : "";
            var htmlText = '<div class="control-group">' +
                '<label class="control-label" for="' + attrs.formId + '">' + attrs.label + '</label>' +
                    '<div class="controls">' +
                    '<input type="' + type + '" class="input-xlarge" id="' + attrs.formId + '" name="' + attrs.formId + '" ' + required + '>' +
                    '</div>' +
                '</div>';
            element.replaceWith(htmlText);
        }
    };
})

6
Це трохи пізно, але якщо htmlTextви додали ng-clickде - то, що тільки модифікація буде замінити element.replaceWith(htmlText)з element.replaceWith($compile(htmlText))?
jclancy

@Misko, ви згадали, щоб позбутися масштабів. Чому? У мене є директива, яка не компілюється при використанні з ізольованою областю.
Сям

1
це не працює, якщо htmlTextмістить директиву ng-transclude
Alp

3
На жаль, я виявив, що перевірка форми не працює з цим, $errorпрапори на вставленому вході ніколи не встановлюються. Я повинен був зробити це в межах властивості посилання директиви: $compile(htmlText)(scope,function(_el){ element.replaceWith(_el); });для того, щоб контролер форми визнав його новостворене існування і включив його в перевірку. Я не міг змусити його працювати у властивості компіляції директиви.
meconroy

5
Гаразд, це 2015 рік там, і я впевнений, що в ручному генеруванні розмітки в скриптах щось не так .
БорисОкунський

38

Намагався використовувати рішення, запропоноване Міськом, але в моїй ситуації деякі атрибути, які потрібно було об'єднати в мій html-шаблон, самі були директивами.

На жаль, не всі директиви, на які посилається отриманий шаблон, працювали коректно. У мене не було достатньо часу, щоб зануритися в кутовий код і з’ясувати першопричину, але я знайшов рішення, яке може бути корисним.

Рішення полягало в тому, щоб перемістити код, який створює html-шаблон, з компілювати до функції шаблону. Приклад на основі коду зверху:

    angular.module('formComponents', [])
  .directive('formInput', function() {
    return {
        restrict: 'E',
        template: function(element, attrs) {
           var type = attrs.type || 'text';
            var required = attrs.hasOwnProperty('required') ? "required='required'" : "";
            var htmlText = '<div class="control-group">' +
                '<label class="control-label" for="' + attrs.formId + '">' + attrs.label + '</label>' +
                    '<div class="controls">' +
                    '<input type="' + type + '" class="input-xlarge" id="' + attrs.formId + '" name="' + attrs.formId + '" ' + required + '>' +
                    '</div>' +
                '</div>';
             return htmlText;
        }
        compile: function(element, attrs)
        {
           //do whatever else is necessary
        }
    }
})

Це вирішило мою проблему із вбудованим ng-клацанням у шаблоні
joshcomley

Дякую, це працювало і для мене. Хотіла завершити директиву, щоб застосувати деякі атрибути за замовчуванням.
мартінос

2
Дякую, я навіть не знав, що шаблон прийняв функцію!
Джон Сноу

2
Це не обхідне рішення. Це правильна відповідь на ОП. Умовно складання шаблону залежно від атрибутів елемента є точною метою функції директиви / компонента шаблону. Не слід використовувати компіляцію для цього. Команда Angular сильно заохочує цей стиль кодування (не використовуючи функції компіляції).
jose.angel.jimenez

Це має бути правильна відповідь, навіть я не знав, що шаблон виконує функцію :)
NeverGiveUp161

5

Наведені вище відповіді, на жаль, не дуже працюють. Зокрема, етап компіляції не має доступу до області застосування, тому ви не можете налаштувати поле на основі динамічних атрибутів. Використання етапу зв’язування, здається, пропонує найбільшу гнучкість (з точки зору асинхронного створення дому тощо). Підхід, наведений нижче, стосується:

<!-- Usage: -->
<form>
  <form-field ng-model="formModel[field.attr]" field="field" ng-repeat="field in fields">
</form>
// directive
angular.module('app')
.directive('formField', function($compile, $parse) {
  return { 
    restrict: 'E', 
    compile: function(element, attrs) {
      var fieldGetter = $parse(attrs.field);

      return function (scope, element, attrs) {
        var template, field, id;
        field = fieldGetter(scope);
        template = '..your dom structure here...'
        element.replaceWith($compile(template)(scope));
      }
    }
  }
})

Я створив суть з більш повним кодом та описом підходу.


приємний підхід. до жаль , при використанні з ngTransclude я отримую наступне повідомлення про помилку:Error: [ngTransclude:orphan] Illegal use of ngTransclude directive in the template! No parent directive that requires a transclusion found.
Алп

а чому б не використовувати ізольований діапазон з 'полем:' = ''?
IttayD

Дуже приємно, дякую! На жаль, ваш письмовий підхід офлайн :(
Michiel

І суть, і запис - це мертві ланки.
бінкі

4

Ось що я в кінцевому підсумку використав.

Я дуже новачок у AngularJS, тому хотів би бачити кращі / альтернативні рішення.

angular.module('formComponents', [])
    .directive('formInput', function() {
        return {
            restrict: 'E',
            scope: {},
            link: function(scope, element, attrs)
            {
                var type = attrs.type || 'text';
                var required = attrs.hasOwnProperty('required') ? "required='required'" : "";
                var htmlText = '<div class="control-group">' +
                    '<label class="control-label" for="' + attrs.formId + '">' + attrs.label + '</label>' +
                        '<div class="controls">' +
                        '<input type="' + type + '" class="input-xlarge" id="' + attrs.formId + '" name="' + attrs.formId + '" ' + required + '>' +
                        '</div>' +
                    '</div>';
                element.html(htmlText);
            }
        }
    })

Приклад використання:

<form-input label="Application Name" form-id="appName" required/></form-input>
<form-input type="email" label="Email address" form-id="emailAddress" required/></form-input>
<form-input type="password" label="Password" form-id="password" /></form-input>

10
Кращим рішенням є: (1) використовувати функцію компіляції замість функції зв’язування та зробити заміну там. Шаблон не працюватиме у вашому випадку, оскільки ви хочете його налаштувати. (2) позбутися сфери застосування:
Місько Гевери

@MiskoHevery Дякую за відгук - ви б не хотіли пояснити, чому функція компіляції надає перевагу функції посилання тут?
Марті Пітт

4
Я думаю, що це відповідь від docs.angularjs.org/guide/directive : "Будь-яка операція, яку можна поділити між екземпляром директив [наприклад, перетворення шаблону DOM], слід перенести на функцію компіляції з міркувань продуктивності."
Марк Райкок

@Marty Ви все ще можете прив'язати один із своїх власних входів до моделі? (тобто <form-input ng-model="appName" label="Application Name" form-id="appName" required/></form-input>)
Джонатан Вілсон

1
@MartyPitt з книги "AngularJS" O'Reilly: "Отже, у нас з'явилася compileфаза, яка стосується перетворення шаблону, і linkфаза, яка стосується зміни даних у поданні. Вздовж цих ліній головна різниця між compileа linkфункції директив полягають у тому, що compileфункції стосуються трансформації самого шаблону, а linkфункції стосуються встановлення динамічного зв’язку між моделлю та поданням. Саме на цій другій фазі області приєднуються до складених linkфункцій, і директива стає живою через прив'язку даних "
Джуліан
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.