Передайте форму директиві


75

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

<div ng-form='myForm'>
  <my-input name='Email' type='email' label='Email Address' placeholder="Enter email" ng-model='model.email' required='false'></my-input>

</div>

Як отримати доступ до myFormмоєї директиви, щоб я міг перевірити перевірку, наприклад myForm.Email.$valid?


на основі відповідей нижче angular не підтримує динамічні імена елементів форми.
Емад,

Однак є обхідний шлях. Подивіться тут Планк , він базується на відповіді @tanguy_k
Ruslans Uralovs

Відповіді:


154

Щоб отримати доступ до FormController в директиві:

require: '^form',

Тоді він буде доступний як 4-й аргумент для вашої функції посилання:

link: function(scope, element, attrs, formCtrl) {
    console.log(formCtrl);
}

fiddle

Вам може знадобитися лише доступ до NgModelController, хоча:

require: 'ngModel',
link: function(scope, element, attrs, ngModelCtrl) {
     console.log(ngModelCtrl);
}

fiddle

Якщо вам потрібен доступ до обох:

require: ['^form','ngModel'],
link: function(scope, element, attrs, ctrls) {
    console.log(ctrls);
}

fiddle


3
Якщо ви не проти відповісти, чому це працює, '^form'а не з '^ngForm'? Я намагався змусити це працювати з першого разу, але це привело до помилки "немає контролера". Мені подобається ваша відповідь набагато більше, ніж моїй.
OverZealous

9
@OverZealous, очевидно, ім'я директиви, яке Angular використовує, коли в HTML знайдено директиву formабо, ng-formє formне ngForm. Мені знадобилося кілька спроб з’ясувати, що це ім’я form. Я думаю, що це вихідний код Angular, де ми бачимо, що formвін використовується.
Mark Rajcok

2
@eibrahim, Angular не підтримує динамічні імена елементів форми.
Mark Rajcok

1
@MarkRajcok Доступ до батьківської форми всередині директиви - це лише половина роботи. Потім потрібно використовувати $ invalid, $ error та друзів. ~~ І це не працює, єдиним рішенням є створення ng-форми всередині директиви ~~ /! \ Я помилявся, див. Мою відповідь нижче: stackoverflow.com/questions/17618318/pass-form-to-directive /…
tanguy_k

3
@mtpultz, поклав його на обсяг у вашій функції зв'язку: scope.formCtrl = formCtrl;, то ви можете отримати доступ до нього в вашому контролері з допомогою $scope: controller: function($scope) { ... }. Однак зверніть увагу, що ваш контролер директив буде запускатися першим, тому посилання не буде там, коли функція контролера виконується вперше.
Mark Rajcok,

32

Ось повний приклад (стилізований за допомогою Bootstrap 3.1)

Він містить форму із кількома введеними даними (ім’я, електронна адреса, вік та країна). Ім'я, електронна пошта та вік - це директиви. Країна є "регулярним" входом.

Для кожного вводу відображається довідкове повідомлення, коли користувач не вводить правильне значення.

Форма містить кнопку збереження, яка вимикається, якщо форма містить хоча б одну помилку.

<!-- index.html -->
<body ng-controller="AppCtrl">
  <script>
    var app = angular.module('app', []);

    app.controller('AppCtrl', function($scope) {
      $scope.person = {};
    });
  </script>
  <script src="inputName.js"></script>
  <script src="InputNameCtrl.js"></script>
  <!-- ... -->

  <form name="myForm" class="form-horizontal" novalidate>
    <div class="form-group">
      <input-name ng-model='person.name' required></input-name>
    </div>

    <!-- ... -->

    <div class="form-group">
      <div class="col-sm-offset-2 col-sm-4">
        <button class="btn btn-primary" ng-disabled="myForm.$invalid">
          <span class="glyphicon glyphicon-cloud-upload"></span> Save
        </button>
      </div>
    </div>
  </form>

  Person: <pre>{{person | json}}</pre>
  Form $error: <pre>{{myForm.$error | json}}</pre>
  <p>Is the form valid?: {{myForm.$valid}}</p>
  <p>Is name valid?: {{myForm.name.$valid}}</p>
</body>

// inputName.js
app.directive('inputName', function() {
  return {
    restrict: 'E',
    templateUrl: 'input-name.html',
    replace: false,
    controller: 'InputNameCtrl',
    require: ['^form', 'ngModel'],

    // See Isolating the Scope of a Directive http://docs.angularjs.org/guide/directive#isolating-the-scope-of-a-directive
    scope: {},

    link: function(scope, element, attrs, ctrls) {
      scope.form = ctrls[0];
      var ngModel = ctrls[1];

      if (attrs.required !== undefined) {
        // If attribute required exists
        // ng-required takes a boolean
        scope.required = true;
      }

      scope.$watch('name', function() {
        ngModel.$setViewValue(scope.name);
      });
    }
  };
});

// inputNameCtrl
app.controller('InputNameCtrl', ['$scope', function($scope) {
}]);

Форма AngularJS з директивами


2
Це чудовий приклад. Я змінив ваш Plunk, щоб зробити вашу директиву більш загальною, оскільки тепер вона може підтримувати будь-який тип введення, наприклад, текст, пароль, електронну пошту тощо. Дивіться тут: plnkr.co/edit/13rqpfrTiTwDMpCPmT7X?p=preview
Русланс Ураловс

здається, це не працює, коли у вас одна і та ж директива кілька разів на одному і тому ж поданні - це тому, що "ім'я" жорстко закодовано в директиві
Марті

У підсумку мені довелося мати вкладені форми, тому що мені потрібно мати одну і ту ж директиву кілька разів у поданні, див .: stackoverflow.com/questions/14378401/…
Марті

Це працює дуже приємно, дякую за публікацію. Хоча зараз я маю справу з оберненою проблемою: при встановленні значення об'єкта поза сферою дії директиви, він не оновлює інтерфейс і "заморожує" дані, ніколи більше не змінюючись. Приклад тут: plnkr.co/edit/HLKKY1ZH0Kla93P2SmGj?p=preview Будь-яка підказка про те, як це вирішити?
Родріго Бранчер

На жаль, я забув згадати (невелику) зміну Plunk: я додав посилання праворуч від кнопки подати , яке програмно змінює ім’я людини.
Родріго Бранчер

17

Редагування 2: Я залишу свою відповідь, оскільки це може бути корисно з інших причин, але інша відповідь від Марка Райкока - це те, що я спочатку хотів зробити, але не зміг приступити до роботи. Очевидно, батьківський контролер тут буде form, ні ngForm.


Ви можете передати його, використовуючи атрибут у своїй директиві, хоча це буде досить багатослівно.

Приклад

Ось робочий спрощений jsFiddle .

Код

HTML:

<div ng-form="myForm">
    <my-input form="myForm"></my-input>
</div>

Основні частини директиви:

app.directive('myInput', function() {
    return {
        scope: {
            form: '='
        },
        link: function(scope, element, attrs) {
            console.log(scope.form);
        }
    };
});

Що відбувається

Ми попросили Angular прив'язати значення області, названу в formатрибуті, до нашої ізольованої області, використовуючи '='.

Робить це таким чином, відокремлює фактичну форму від директиви введення.

Примітка: Я намагався використовувати require: "^ngForm", але директива ngForm не визначає контролер і не може бути використана таким чином (що дуже погано).


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

Змінити: Використання батьківської директиви

Добре, ось найкраще, що я міг зрозуміти, використовуючи батьківську директиву, я поясню більше через секунду:

Робота jsFiddle з використанням батьківської директиви

HTML:

<div ng-app="myApp">
    <div ng-form="theForm">
        <my-form form="theForm">
            <my-input></my-input>
        </my-form>
    </div>
</div>

JS (частково):

app.directive('myForm', function() {
    return {
        restrict: 'E',
        scope: {
            form: '='
        },
        controller: ['$scope', function($scope) {
            this.getForm = function() {
                return $scope.form;
            }
        }]
    }
});

app.directive('myInput', function() {
    return {
        require: '^myForm',
        link: function(scope, element, attrs, myForm) {
            console.log(myForm.getForm());
        }
    };
});

Це зберігає форму в області батьківської директиви ( myForm) і дозволяє дочірнім директивам отримувати до неї доступ, вимагаючи батьківську форму ( require: '^myForm') та отримуючи доступ до контролера директиви у функції зв’язку (myForm.getForm() ).

Переваги:

  • Потрібно лише визначити форму в одному місці
  • Ви можете використовувати батьківський контролер для розміщення загального коду

Негативні:

  • Вам потрібен додатковий вузол
  • Потрібно двічі вказати назву форми

Що я б віддав перевагу

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

Однак я отримував якусь дивну поведінку із сферою дії, де myFormNameзмінна буде видно всередині $scope, але це буде undefinedтоді, коли я спробую отримати до неї доступ. Цей мене розгубив.


дякую за відповідь ... проблема в моїй директиві, я можу отримати scope.form, але я не можу дістатися до scope.form.Email
Emad

це те, що я роблю jsfiddle.net/SwEM6/1 (зауважте, що ім'я вводу "обчислюється" за властивістю області {{name}} директиви, але якщо я зміню це на жорстко закодоване значення, наприклад "Email", тоді це працює ... будь-яка ідея?
Емад

Ви шукаєте спосіб зробити динамічні імена форм, тобто name="{{name1}}", і я ще не побачив відповіді на це.
rGil

1
Розмовляв занадто рано. Це рішення може працювати для вас, але трохи залучене. @ Overzealous, можливо, ви захочете додати це до своєї відповіді.
rGil

Я б подивився на відповідь Марка Райкока нижче. Я, мабуть, не націлив правильну директиву.
OverZealous

8

Починаючи з AngularJS 1.5.0, для цього є набагато більш чисте рішення (на відміну від linkбезпосереднього використання функції). Якщо ви хочете отримати доступ до форм FormControllerу контролері директив вашого підкомпонента, ви можете просто вдарити requireатрибут директиви, наприклад:

return {
  restrict : 'EA',
  require : {
    form : '^'
  },
  controller : MyDirectiveController,
  controllerAs : 'vm',
  bindToController : true,
  ...
};

Далі ви зможете отримати до нього доступ у своєму шаблоні або контролері директив, як і будь-яка інша змінна області, наприклад :

function MyDirectiveController() {
  var vm = this;
  console.log('Is the form valid? - %s', vm.form.$valid);
}

Зверніть увагу, що для того, щоб це працювало, вам також потрібно встановити bindToController: trueатрибут у вашій директиві. Див документацію для $compileі це питання для отримання додаткової інформації.

Відповідні частини з документації:

вимагати

Вимагати іншої директиви та вводити її контролер як четвертий аргумент функції зв’язування. Властивістю require може бути рядок , масив або об’єкт :

Якщо властивість require є об'єктом і bindToControllerє істинним, то необхідні контролери прив'язані до контролера за допомогою ключів властивості require. Якщо ім'я необхідного контролера збігається з місцевим іменем (ключем), ім'я можна опустити. Наприклад, {parentDir: '^parentDir'}еквівалентно {parentDir: '^'}.


Це виглядало майже правильно, але для мене не зовсім працює. Якщо у мене просто є необхідна властивість, вона буде передана у функцію зв’язку, але не пов’язана з контролером (bindToController відповідає істині). Це через неізоляцію сфери застосування?
Даррен Кларк

Працюйте нормально і дуже елегантно. Для мене це не працює з {parentDir: '^'}, а лише з {parentDir: '^ parentDir'}.
Джові

Це було чудовою відправною точкою для мене. Однак я зіткнувся з проблемами, оскільки моя директива має ізольований обсяг , у цьому випадку я не використовував bindToController і використовував контролери require у 4-му аргументі блоку посилання .
Габріель Гейтс,

2

Зробив свою скрипкову роботу "Що б я віддав перевагу"! З якоїсь причини ви могли бачити рядок "$ scope.ngForm" у console.log, але його реєстрація безпосередньо не спрацювала, що призвело до невизначеності. Однак ви можете отримати його, якщо передасте атрибути функції контролера.

app.directive('myForm', function() {
return {
    restrict: 'A',
    controller: ['$scope','$element','$attrs', function($scope,$element,$attrs) {
        this.getForm = function() {
            return $scope[$attrs['ngForm']];
        }
    }]
}
});

http://jsfiddle.net/vZ6MD/20/

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.