Как проверить входные данные, динамически созданные с использованием ng-repeat, ng-show (angular)
У меня есть таблица, созданная с помощью ng-repeat. Я хочу добавить проверку для каждого элемента в таблице. Проблема в том, что каждая входная ячейка имеет то же имя, что и ячейка над и под ней. Я пытался использовать {{$index}}
значение для ввода входных данных, но, несмотря на то, что строковые литералы в HTML выглядят правильно, теперь оно работает.
Вот мой код на данный момент:
<tr ng-repeat="r in model.BSM ">
<td>
<input ng-model="r.QTY" class="span1" name="QTY{{$index}}" ng-pattern="/^[\d]*\.?[\d]*$/" required/>
<span class="alert-error" ng-show="form.QTY{{$index}}.$error.pattern"><strong>Requires a number.</strong></span>
<span class="alert-error" ng-show="form.QTY{{$index}}.$error.required"><strong>*Required</strong></span>
</td>
</tr>
Я пытался удалить {{}}
из индекса, но это тоже не работает. На данный момент свойство проверки входных данных работает правильно, но сообщение об ошибке не отображается.
У кого-нибудь есть предложения?
Изменить: В дополнение к отличным ответам ниже, в статье блога, которая более подробно освещает эту проблему: http://www.thebhwgroup.com/blog/2014/08/angularjs-html-form-design-part-2/
14 ответов
AngularJS использует входные имена для выявления ошибок валидации.
К сожалению, на сегодняшний день невозможно (без использования пользовательской директивы) динамически генерировать имя входа. Действительно, проверяя входные документы, мы видим, что атрибут name принимает только строку.
Чтобы решить проблему с "динамическим именем", вам нужно создать внутреннюю форму (см. Ng-form):
<div ng-repeat="social in formData.socials">
<ng-form name="urlForm">
<input type="url" name="socialUrl" ng-model="social.url">
<span class="alert error" ng-show="urlForm.socialUrl.$error.url">URL error</span>
</ng-form>
</div>
Другой альтернативой будет написать специальную директиву для этого.
Вот jsFiddle, показывающий использование ngForm: http://jsfiddle.net/pkozlowski_opensource/XK2ZT/2/
Так как вопрос был задан, команда Angular решила эту проблему, сделав возможным динамическое создание входных имен.
С версией Angular 1.3 и выше вы можете сделать это:
<form name="vm.myForm" novalidate>
<div ng-repeat="p in vm.persons">
<input type="text" name="person_{{$index}}" ng-model="p" required>
<span ng-show="vm.myForm['person_' + $index].$invalid">Enter a name</span>
</div>
</form>
Angular 1.3 также представил ngMessages, более мощный инструмент для проверки формы. Вы можете использовать ту же технику с ngMessages:
<form name="vm.myFormNgMsg" novalidate>
<div ng-repeat="p in vm.persons">
<input type="text" name="person_{{$index}}" ng-model="p" required>
<span ng-messages="vm.myFormNgMsg['person_' + $index].$error">
<span ng-message="required">Enter a name</span>
</span>
</div>
</form>
Если вы не хотите использовать ng-form, вы можете использовать пользовательскую директиву, которая изменит атрибут name формы. Поместите эту директиву как атрибут в тот же элемент, что и ваша ng-модель.
Если вы используете другие директивы вместе, будьте осторожны, чтобы у них не было установлено свойство "терминала", иначе эта функция не сможет работать (учитывая, что она имеет приоритет -1).
Например, при использовании этой директивы с ng-options, вы должны запустить этот однострочный monkeypatch: https://github.com/AlJohri/bower-angular/commit/eb17a967b7973eb7fc1124b024aa8b3ca540a155
angular.module('app').directive('fieldNameHack', function() {
return {
restrict: 'A',
priority: -1,
require: ['ngModel'],
// the ngModelDirective has a priority of 0.
// priority is run in reverse order for postLink functions.
link: function (scope, iElement, iAttrs, ctrls) {
var name = iElement[0].name;
name = name.replace(/\{\{\$index\}\}/g, scope.$index);
var modelCtrl = ctrls[0];
modelCtrl.$name = name;
}
};
});
Я часто нахожу полезным использовать ng-init для установки $index на имя переменной. Например:
<fieldset class='inputs' ng-repeat="question questions" ng-init="qIndex = $index">
Это изменит ваше регулярное выражение на:
name = name.replace(/\{\{qIndex\}\}/g, scope.qIndex);
Если у вас есть несколько вложенных ng-повторов, теперь вы можете использовать эти имена переменных вместо $parent.$ Index.
Определение "терминала" и "приоритета" для директив: https://docs.angularjs.org/api/ng/service/ $ compile # directive-definition-object
Комментарий Github относительно необходимости в ng-option:
ОБНОВИТЬ:
Вы также можете сделать это с помощью ng-формы.
angular.module('app').directive('formNameHack', function() {
return {
restrict: 'A',
priority: 0,
require: ['form'],
compile: function() {
return {
pre: function(scope, iElement, iAttrs, ctrls) {
var parentForm = $(iElement).parent().controller('form');
if (parentForm) {
var formCtrl = ctrls[0];
delete parentForm[formCtrl.$name];
formCtrl.$name = formCtrl.$name.replace(/\{\{\$index\}\}/g, scope.$index);
parentForm[formCtrl.$name] = formCtrl;
}
}
}
}
};
});
Используйте директиву ng-form внутри тега, в котором вы используете директиву ng-repeat. Затем вы можете использовать область, созданную директивой ng-form, для ссылки на общее имя. Например:
<div class="form-group col-sm-6" data-ng-form="subForm" data-ng-repeat="field in justificationInfo.justifications"">
<label for="{{field.label}}"><h3>{{field.label}}</h3></label>
<i class="icon-valid" data-ng-show="subForm.input.$dirty && subForm.input.$valid"></i>
<i class="icon-invalid" data-ng-show="subForm.input.$dirty && subForm.input.$invalid"></i>
<textarea placeholder="{{field.placeholder}}" class="form-control" id="{{field.label}}" name="input" type="text" rows="3" data-ng-model="field.value" required>{{field.value}}</textarea>
</div>
Кредит: http://www.benlesh.com/2013/03/angular-js-validating-form-elements-in.html
Добавлен более сложный пример с "пользовательской проверкой" на стороне контроллера http://jsfiddle.net/82PX4/3/
<div class='line' ng-repeat='line in ranges' ng-form='lineForm'>
low: <input type='text'
name='low'
ng-pattern='/^\d+$/'
ng-change="lowChanged(this, $index)" ng-model='line.low' />
up: <input type='text'
name='up'
ng-pattern='/^\d+$/'
ng-change="upChanged(this, $index)"
ng-model='line.up' />
<a href ng-if='!$first' ng-click='removeRange($index)'>Delete</a>
<div class='error' ng-show='lineForm.$error.pattern'>
Must be a number.
</div>
<div class='error' ng-show='lineForm.$error.range'>
Low must be less the Up.
</div>
</div>
Рассматривая эти решения, предложенное Аль Джохри выше всего соответствует моим потребностям, но его директива была немного менее программируемой, чем я хотел. Вот моя версия его решения:
angular.module("app", [])
.directive("dynamicFormName", function() {
return {
restrict: "A",
priority: 0,
require: ["form"],
compile: function() {
return {
pre: function preLink(scope, iElement, iAttrs, ctrls) {
var name = "field" + scope.$index;
if (iAttrs.dnfnNameExpression) {
name = scope.$eval(iAttrs.dnfnNameExpression);
}
var parentForm = iElement.parent().controller("form");
if (parentForm) {
var formCtrl = ctrls[0];
delete parentForm[formCtrl.$name];
formCtrl.$name = name;
parentForm[formCtrl.$name] = formCtrl;
}
}
}
}
};
});
Это решение позволяет вам просто передать выражение генератора имени в директиву и избежать блокировки замены шаблона, которую он использовал.
Сначала у меня также были проблемы с этим решением, поскольку оно не показывало пример использования его в разметке, поэтому вот как я его использовал.
<form name="theForm">
<div ng-repeat="field in fields">
<input type="number" ng-form name="theInput{{field.id}}" ng-model="field.value" dynamic-form-name dnfn-name-expression="'theInput' + field.id">
</div>
</form>
У меня есть более полный рабочий пример на github.
Проверка работает с повторением ng, если я использую следующий синтаксис scope.step3Form['item[107][quantity]'].$touched
Я не знаю, это лучшая практика или лучшее решение, но оно работает
<tr ng-repeat="item in items">
<td>
<div class="form-group">
<input type="text" ng-model="item.quantity" name="item[<% item.id%>][quantity]" required="" class="form-control" placeholder = "# of Units" />
<span ng-show="step3Form.$submitted || step3Form['item[<% item.id %>][quantity]'].$touched">
<span class="help-block" ng-show="step3Form['item[<% item.id %>][quantity]'].$error.required"> # of Units is required.</span>
</span>
</div>
</td>
</tr>
Это слишком поздно, но может быть, это может помочь любому
- Создайте уникальное имя для каждого элемента управления
- Подтвердить с помощью
fromname[uniquname].$error
Образец кода:
<input
ng-model="r.QTY"
class="span1"
name="QTY{{$index}}"
ng-pattern="/^[\d]*\.?[\d]*$/" required/>
<div ng-messages="formName['QTY' +$index].$error"
ng-show="formName['QTY' +$index].$dirty || formName.$submitted">
<div ng-message="required" class='error'>Required</div>
<div ng-message="pattern" class='error'>Invalid Pattern</div>
</div>
Смотрите рабочую демо здесь
Если вы используете ng-repeat $index, работает так
name="QTY{{$index}}"
а также
<td>
<input ng-model="r.QTY" class="span1" name="QTY{{$index}}" ng-
pattern="/^[\d]*\.?[\d]*$/" required/>
<span class="alert-error" ng-show="form['QTY' + $index].$error.pattern">
<strong>Requires a number.</strong></span>
<span class="alert-error" ng-show="form['QTY' + $index].$error.required">
<strong>*Required</strong></span>
</td>
мы должны показать нг-шоу в нг-шаблоне
<span class="alert-error" ng-show="form['QTY' + $index].$error.pattern">
<span class="alert-error" ng-show="form['QTY' + $index].$error.required">
Основываясь на ответе pkozlowski.opensource, я добавил способ иметь динамические входные имена, которые также работают с ngMessages. Обратите внимание ng-init
часть на ng-form
элемент и использование furryName
, furryName
становится именем переменной, содержащей значение переменной для input
"s name
приписывать.
<ion-item ng-repeat="animal in creatures track by $index">
<ng-form name="animalsForm" ng-init="furryName = 'furry' + $index">
<!-- animal is furry toggle buttons -->
<input id="furryRadio{{$index}}"
type="radio"
name="{{furryName}}"
ng-model="animal.isFurry"
ng-value="radioBoolValues.boolTrue"
required
>
<label for="furryRadio{{$index}}">Furry</label>
<input id="hairlessRadio{{$index}}"
name="{{furryName}}"
type="radio"
ng-model="animal.isFurry"
ng-value="radioBoolValues.boolFalse"
required
>
<label for="hairlessRadio{{$index}}">Hairless</label>
<div ng-messages="animalsForm[furryName].$error"
class="form-errors"
ng-show="animalsForm[furryName].$invalid && sectionForm.$submitted">
<div ng-messages-include="client/views/partials/form-errors.ng.html"></div>
</div>
</ng-form>
</ion-item>
Вот пример того, как я это делаю, я не знаю, является ли это лучшим решением, но работает отлично.
Во-первых, код в HTML. Посмотрите на ng-класс, он вызывает функцию hasError. Посмотрите также на объявление имени входа. Я использую $index для создания разных входных имен.
<div data-ng-repeat="tipo in currentObject.Tipo"
ng-class="{'has-error': hasError(planForm, 'TipoM', 'required', $index) || hasError(planForm, 'TipoM', 'maxlength', $index)}">
<input ng-model="tipo.Nombre" maxlength="100" required
name="{{'TipoM' + $index}}"/>
А теперь вот функция hasError:
$scope.hasError = function (form, elementName, errorType, index) {
if (form == undefined
|| elementName == undefined
|| errorType == undefined
|| index == undefined)
return false;
var element = form[elementName + index];
return (element != null && element.$error[errorType] && element.$touched);
};
Это приведет к тому, что имя в ng-repeat появится отдельно в проверке формы.
<td>
<input ng-model="r.QTY" class="span1" name="{{'QTY' + $index}}" ng-pattern="/^[\d]*\.?[\d]*$/" required/>
</td>
Но у меня были проблемы с его поиском в проверочном сообщении, поэтому мне пришлось использовать ng-init, чтобы получить разрешение переменной в качестве ключа объекта.
<td>
<input ng-model="r.QTY" class="span1" ng-init="name = 'QTY' + $index" name="{{name}}" ng-pattern="/^[\d]*\.?[\d]*$/" required/>
<span class="alert-error" ng-show="form[name].$error.pattern"><strong>Requires a number.</strong></span>
<span class="alert-error" ng-show="form[name].$error.required"><strong>*Required</strong></span>
Мои требования немного отличались от тех, которые были заданы в первоначальном вопросе, но, надеюсь, я мог бы помочь кому-то, кто переживает ту же проблему, что и я...
Я должен был определить, является ли поле обязательным или нет, основываясь на переменной области видимости. Поэтому я в основном должен был установить ng-required="myScopeVariable"
(которая является логической переменной).
<div class="align-left" ng-repeat="schema in schemas">
<input type="text" ng-required="schema.Required" />
</div>
Это возможно, и вот как я делаю то же самое с таблицей входов.
оберните стол в такую форму
Тогда просто используйте это
У меня есть форма с несколькими вложенными директивами, которые содержат входные данные, выбор (ы) и т. Д.... Все эти элементы заключены в ng-повторы и динамические строковые значения.
Вот как использовать директиву:
<form name="myFormName">
<nested directives of many levels>
<your table here>
<perhaps a td here>
ex: <input ng-repeat=(index, variable) in variables" type="text"
my-name="{{ variable.name + '/' + 'myFormName' }}"
ng-model="variable.name" required />
ex: <select ng-model="variable.name" ng-options="label in label in {{ variable.options }}"
my-name="{{ variable.name + index + '/' + 'myFormName' }}"
</select>
</form>
Примечание: вы можете добавлять и индексировать конкатенацию строк, если вам нужно сериализовать, возможно, таблицу входов; что я и сделал
app.directive('myName', function(){
var myNameError = "myName directive error: "
return {
restrict:'A', // Declares an Attributes Directive.
require: 'ngModel', // ngModelController.
link: function( scope, elem, attrs, ngModel ){
if( !ngModel ){ return } // no ngModel exists for this element
// check myName input for proper formatting ex. something/something
checkInputFormat(attrs);
var inputName = attrs.myName.match('^\\w+').pop(); // match upto '/'
assignInputNameToInputModel(inputName, ngModel);
var formName = attrs.myName.match('\\w+$').pop(); // match after '/'
findForm(formName, ngModel, scope);
} // end link
} // end return
function checkInputFormat(attrs){
if( !/\w\/\w/.test(attrs.rsName )){
throw myNameError + "Formatting should be \"inputName/formName\" but is " + attrs.rsName
}
}
function assignInputNameToInputModel(inputName, ngModel){
ngModel.$name = inputName
}
function addInputNameToForm(formName, ngModel, scope){
scope[formName][ngModel.$name] = ngModel; return
}
function findForm(formName, ngModel, scope){
if( !scope ){ // ran out of scope before finding scope[formName]
throw myNameError + "<Form> element named " + formName + " could not be found."
}
if( formName in scope){ // found scope[formName]
addInputNameToForm(formName, ngModel, scope)
return
}
findForm(formName, ngModel, scope.$parent) // recursively search through $parent scopes
}
});
Это должно справиться со многими ситуациями, когда вы просто не знаете, где будет форма. Или, возможно, у вас есть вложенные формы, но по какой-то причине вы хотите прикрепить это входное имя к двум формам? Ну, просто передайте имя формы, к которой вы хотите присоединить имя ввода.
То, что я хотел, - это способ назначать динамические значения для входных данных, которые я никогда не узнаю, а затем просто вызывать $scope.myFormName.$ Valid.
Вы можете добавить все, что пожелаете: больше таблиц, больше форм ввода, вложенные формы, все, что вы хотите. Просто передайте имя формы, по которой вы хотите проверить вводимые данные. Затем при отправке формы спросите, действителен ли $scope.yourFormName.$