Динамічна перевірка та ім'я у формі з AngularJS


98

У мене така форма: http://jsfiddle.net/dfJeN/

Як ви бачите, значення імені для вводу є статично встановленим:

name="username"

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

Потім я намагаюся динамічно встановити значення імені: http://jsfiddle.net/jNWB8/

name="{input.name}"

Потім я застосовую це для своєї перевірки

login.{{input.name}}.$error.required

(ця модель буде використана в ng-повторенні), але моя перевірка форми порушена. Це правильно інтерпретується у моєму браузері (якщо я перевіряю елемент, я побачив login.username. $ Error.required).

Будь-яка ідея?

EDIT: Після входу області в консоль виявляється, що

{{input.name}}

вираз не є інтерполятом. Моя форма як атрибут {{input.name}}, але без імені користувача.

ОНОВЛЕННЯ: Оскільки 1.3.0-rc.3 name = "{{input.name}}" працює як очікувалося. Будь ласка, дивіться # 1404


Після деяких досліджень я виявив це: "Колись сценарій, коли використання ngBind віддається перевазі прив'язці {{express}}, це коли бажано помістити прив'язки до шаблону, який миттєво відображається браузером у сирому стані до того, як Angular його скомпонує". . На цій сторінці docs.angularjs.org/api/ng.directive:ngBind , здається, це вдалий початок для того, що я намагаюся зробити. Ця публікація буде оновлена, якщо я знайду рішення.
IxDay

Є відкритий випуск github github.com/angular/angular.js/isissue/1404
Ярослав

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

Ось стаття в блозі, яка, ймовірно, може допомогти іншим, хто натрапив на цю проблему: thebhwgroup.com/blog/2014/08/angularjs-html-form-design-part-2
PFranchise

Відповіді:


176

Ви не можете робити те, що намагаєтесь зробити так.

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

<form name="outerForm">
<div ng-repeat="item in items">
   <ng-form name="innerForm">
      <input type="text" name="foo" ng-model="item.foo" />
      <span ng-show="innerForm.foo.$error.required">required</span>
   </ng-form>
</div>
<input type="submit" ng-disabled="outerForm.$invalid" />
</form>

На жаль, це просто не добре задокументована особливість Angular.


11
як ти врешті-решт вирішив це? Я досі не бачу, як ця конкретна відповідь стосується вашої проблеми - оскільки вона не відображає динамічно генеровані поля форми та назви?
Оддман

7
Це повне рішення (або вирішення) та запропонований підхід з боку кутової команди (від docs.angularjs.org/api/ng.directive:form ): "Оскільки ви не можете динамічно генерувати атрибут імені вхідних елементів за допомогою інтерполяції, ви повинні загортати кожен набір повторних входів у директиву ngForm і вкладати їх у зовнішній елемент форми. " Кожна вкладена форма має власну сферу, яка дозволяє цьому працювати.
Норемак

2
Цей приклад та пропозиція все ще не стосуються динамічного "імені". Схоже, вони хочуть дозволити вам вкладати динамічно «клоновані» набори полів, але основна назва кожного поля повинна бути статичною.
thinice

2
@thinice Так, це допомагає. З цим рішенням ім'я не повинно бути динамічним. Це може бути все, що завгодно (наприклад, "foo"). Суть у дочірній формі має власний обсяг, тому вирази перевірки можуть просто посилатися на innerForm.foo. $ Error etc.
Джед Річардс

@thinice - Вінтамут має рацію. Немає потреби в динамічних іменах, оскільки ви не надсилаєте форму безпосередньо. Намір полягає в тому, щоб змінити якусь модель, потім POST, що через Ajax. в цей момент динамічні імена вам нічого не збираються. Якщо ви фактично використовуєте HTML-форму подання, ви робите щось дивне / неправильне, і вам знадобиться інший підхід.
Бен Леш

44

Використання вкладеної ngForm дозволяє отримати доступ до певного InputController з шаблону HTML. Однак, якщо ви бажаєте отримати доступ до нього з іншого контролера, це не допоможе.

напр

<script>
  function OuterController($scope) {
    $scope.inputName = 'dynamicName';

    $scope.doStuff = function() {
      console.log($scope.formName.dynamicName); // undefined
      console.log($scope.formName.staticName); // InputController
    }
  }
</script>

<div controller='OuterController'>
  <form name='myForm'>
    <input name='{{ inputName }}' />
    <input name='staticName' />
  </form>
  <a ng-click='doStuff()'>Click</a>
</div>

Я використовую цю директиву для вирішення проблеми:

angular.module('test').directive('dynamicName', function($compile, $parse) {
  return {
    restrict: 'A',
    terminal: true,
    priority: 100000,
    link: function(scope, elem) {
      var name = $parse(elem.attr('dynamic-name'))(scope);
      // $interpolate() will support things like 'skill'+skill.id where parse will not
      elem.removeAttr('dynamic-name');
      elem.attr('name', name);
      $compile(elem)(scope);
    }
  };
});

Тепер ви використовуєте динамічні імена там, де це потрібно, лише атрибут "динамично-имя" замість атрибута "ім'я".

напр

<script>
  function OuterController($scope) {
    $scope.inputName = 'dynamicName';

    $scope.doStuff = function() {
      console.log($scope.formName.dynamicName); // InputController
      console.log($scope.formName.staticName); // InputController
    }
  }
</script>

<div controller='OuterController'>
  <form name='myForm'>
    <input dynamic-name='inputName' />
    <input name='staticName' />
  </form>
  <a ng-click='doStuff()'>Click</a>
</div>

1
Я використав це рішення за винятком використання $interpolateзамість $parse, відчув себе більш корисним
TheRocketSurgeon

я бачу, ти робиш терміальне: правда. Що це означає? Чи можу я використовувати цю директиву щодо форм також <form ng-repeat="item in items" dynamic-name="'item'+item.id"> ... <span ng-show="item{{item.id}}.$invalid">This form is invalid</span></form>?
felixfbecker

16

Проблема повинна бути вирішена у AngularJS 1.3, згідно з цим обговоренням на Github .

Тим часом, ось тимчасове рішення, створене @caitp та @Thinkscape :

// Workaround for bug #1404
// https://github.com/angular/angular.js/issues/1404
// Source: http://plnkr.co/edit/hSMzWC?p=preview
app.config(['$provide', function($provide) {
    $provide.decorator('ngModelDirective', function($delegate) {
        var ngModel = $delegate[0], controller = ngModel.controller;
        ngModel.controller = ['$scope', '$element', '$attrs', '$injector', function(scope, element, attrs, $injector) {
            var $interpolate = $injector.get('$interpolate');
            attrs.$set('name', $interpolate(attrs.name || '')(scope));
            $injector.invoke(controller, this, {
                '$scope': scope,
                '$element': element,
                '$attrs': attrs
            });
        }];
        return $delegate;
    });
    $provide.decorator('formDirective', function($delegate) {
        var form = $delegate[0], controller = form.controller;
        form.controller = ['$scope', '$element', '$attrs', '$injector', function(scope, element, attrs, $injector) {
            var $interpolate = $injector.get('$interpolate');
            attrs.$set('name', $interpolate(attrs.name || attrs.ngForm || '')(scope));
            $injector.invoke(controller, this, {
                '$scope': scope,
                '$element': element,
                '$attrs': attrs
            });
        }];
        return $delegate;
    });
}]);

Демо на JSFiddle .


1
Для тих, хто застряг на 1.2, це легко найменш "хакі".
граната

14

Хороший один від @EnISeeK .... але я зрозумів, що він буде більш елегантним і менш нав'язливим до інших директив:

.directive("dynamicName",[function(){
    return {
        restrict:"A",
        require: ['ngModel', '^form'],
        link:function(scope,element,attrs,ctrls){
            ctrls[0].$name = scope.$eval(attrs.dynamicName) || attrs.dynamicName;
            ctrls[1].$addControl(ctrls[0]);
        }
    };
}])

1
Я хотів би додати лише наступне. ctrls [0]. $ name = область. $ eval (attrs.dynamicName) || attrs.dynamicName;
GnrlBzik

7

Лише невелике вдосконалення щодо рішення EnlSeek

angular.module('test').directive('dynamicName', ["$parse", function($parse) {
 return {
    restrict: 'A',
    priority: 10000, 
    controller : ["$scope", "$element", "$attrs", 
           function($scope, $element, $attrs){
         var name = $parse($attrs.dynamicName)($scope);
         delete($attrs['dynamicName']);
         $element.removeAttr('data-dynamic-name');
         $element.removeAttr('dynamic-name');
          $attrs.$set("name", name);
    }]

  };
}]);

Ось випробування викрадення . Ось детальне пояснення


+1, директива EnlSeek викликала нескінченний цикл у моїй директиві; Мені довелося видалити шрифти з цієї відповіді, щоб змусити її працювати
Джон

Пріоритет може перешкоджати набору полів, які мали би те саме ім'я, але мають ng-if. наприклад: <type type = 'text' динамично-имя = 'foo' ng-if = 'field.type == "text" /> <textarea dinami-name =' foo 'ng-if =' field.type == "textarea"> </textarea> Видалення "пріоритету: 10000" вирішило проблему для мене, але, як видається, функціонує правильно.
thinice

ngIf має пріоритет 600. Призначте пріоритет, менший за 600 для цієї директиви, повинен змусити її працювати разом з ngIf.
Jason zhang

Якщо пріоритет не встановлено (за замовчуванням до 0), він може працювати з ngModel (пріоритет 0), якщо ця директива буде оцінена перед ngModel. Ви хочете надати йому пріоритет, щоб він був завжди перед компіляцією / зв'язанням ngModel.
Jason zhang

5

Я трохи розширюю рішення @caitp та @Thinkscape, щоб дозволити динамічно створювати вкладені ng-форми , як це:

<div ng-controller="ctrl">
    <ng-form name="form">
        <input type="text" ng-model="static" name="static"/>

        <div ng-repeat="df in dynamicForms">
            <ng-form name="form{{df.id}}">
                <input type="text" ng-model="df.sub" name="sub"/>
                <div>Dirty: <span ng-bind="form{{df.id}}.$dirty"></span></div>
            </ng-form>
        </div>

        <div><button ng-click="consoleLog()">Console Log</button></div>
        <div>Dirty: <span ng-bind="form.$dirty"></span></div>
    </ng-form>      
</div>

Ось моя демонстрація на JSFiddle .


4

Я використав розчин Бена Леша, і він добре працює на мене. Але одна з проблем, з якою я зіткнулася, полягала в тому, що коли я додав внутрішню форму за допомогою ng-form, всі стани форми, наприклад, form.$valid, form.$errorтощо, стали невизначеними, якщо я використовував ng-submitдирективу.

Отже, якби у мене це було, наприклад:

<form novalidate ng-submit="saveRecord()" name="outerForm">
    <!--parts of the outer form-->
    <ng-form name="inner-form">
      <input name="someInput">
    </ng-form>
    <button type="submit">Submit</button>
</form>

І в моєму контролері:

$scope.saveRecord = function() {
    outerForm.$valid // this is undefined
}

Тому мені довелося повернутися до використання події звичайного клацання для подання форми, і в цьому випадку необхідно передати об’єкт форми:

<form novalidate name="outerForm">  <!--remove the ng-submit directive-->
    <!--parts of the outer form-->
    <ng-form name="inner-form">
      <input name="someInput">
    </ng-form>
    <button type="submit" ng-click="saveRecord(outerForm)">Submit</button>
</form>

І переглянутий метод контролера:

$scope.saveRecord = function(outerForm) {
    outerForm.$valid // this works
}

Я не зовсім впевнений, чому це так, але, сподіваюся, це комусь допомагає.


3

Ця проблема виправлена ​​в кутовій версії 1.3+ Це правильний синтаксис того, що ви намагаєтесь зробити:

login[input.name].$invalid

0

якщо ми встановимо динамічне ім'я для введення, як показано нижче

<input name="{{dynamicInputName}}" />

тоді ми використовуємо валідацію набору для динамічного імені, як наведений нижче код.

<div ng-messages="login.dynamicInputName.$error">
   <div ng-message="required">
   </div>
</div>
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.