Динамическая проверка и имя в форме с AngularJS


98

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

Как видите, значение имени для входа установлено статически:

name="username"

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

Затем я пытаюсь динамически установить значение имени: http://jsfiddle.net/jNWB8/

name="{input.name}"

Затем я применяю это к своей проверке

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

(этот шаблон будет использоваться в ng-repeat), но моя проверка формы не работает. Он правильно интерпретируется в моем браузере (если я проверю элемент, который я увидел login.username. $ Error.required).

Любая идея ?

РЕДАКТИРОВАТЬ: после регистрации области в консоли кажется, что

{{input.name}}

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

ОБНОВЛЕНИЕ: Начиная с версии 1.3.0-rc.3 name = "{{input.name}}" работает должным образом. См. # 1404


После некоторых исследований я обнаружил следующее: «Когда-то сценарий, в котором использование ngBind предпочтительнее привязки {{expression}}, - это когда желательно поместить привязки в шаблон, который на мгновение отображается браузером в исходном состоянии, прежде чем Angular его скомпилирует» . Эта страница docs.angularjs.org/api/ng.directive:ngBind кажется хорошим началом для того, что я пытаюсь сделать. Этот пост будет обновлен, если я найду решение.
IxDay

Открыта проблема с github github.com/angular/angular.js/issues/1404
Ярослав

Решите ли вы какую-либо из ответов вашу проблему. Если да, отметьте его как ответ, щелкнув отметку рядом с оценкой.
Рикардо Соуза,

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

Ответы:


176

Вы не можете делать то, что пытаетесь сделать таким образом.

Предполагая, что вы пытаетесь сделать, вам нужно динамически добавлять элементы в форму с чем-то вроде ng-repeat, вам нужно использовать вложенную 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
как вы в итоге решили это? Я до сих пор не понимаю, как этот конкретный ответ относится к вашей проблеме - поскольку он не показывает динамически генерируемые поля и имена форм?
Oddman

7
Это полное решение (или обходной путь) и подход, предложенный командой angular (из docs.angularjs.org/api/ng.directive:form ): «Поскольку вы не можете динамически генерировать атрибут имени входных элементов с помощью интерполяции, вы должны заключать каждый набор повторяющихся входных данных в директиву ngForm и вкладывать их во внешний элемент формы ". Каждая вложенная форма имеет собственную область видимости, что позволяет этому работать.
Noremac 05

2
Этот пример и предложение по-прежнему не касаются динамического «имени». Похоже, они хотят позволить вам динамически вкладывать «клонированные» наборы полей, но базовое имя каждого поля должно быть статическим.
thinice

2
@thinice Да, это помогает. В этом решении имя не должно быть динамическим. Это может быть что угодно (например, «foo»). Дело в том, что дочерняя форма имеет свою собственную область видимости, поэтому выражения проверки могут просто ссылаться на innerForm.foo. $ Error и т.д. Затем ng-модель может указывать на все, что вы хотите, в родительской области (возможно, динамически).
Джед Ричардс

@thinice - Винтамут прав. В динамических именах нет необходимости, поскольку вы не отправляете форму напрямую. Намерение состоит в том, чтобы изменить некоторую модель, а затем отправить ее через 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);
    }
  };
});

Теперь вы используете динамические имена везде, где это необходимо, просто атрибут «dynamic-name» вместо атрибута «name».

например

<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,
мне

я вижу, что вы делаете термин: правда. Что это значит? Могу ли я использовать эту директиву и в формах, например <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

Согласно этому обсуждению на Github, проблема должна быть исправлена ​​в AngularJS 1.3 .

Между тем, вот временное решение, созданное @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
Для тех, кто застрял на ng 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 = scope. $ 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. например: <input type = 'text' dynamic-name = 'foo' ng-if = 'field.type == "text" /> <textarea dynamic-name =' foo 'ng-if =' field.type == "textarea"> </textarea> Удаление 'priority: 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

Эта проблема исправлена ​​в Angular 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.