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


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.