html - example - Cómo validar las entradas creadas dinámicamente utilizando ng-repeat, ng-show(angular)
angular grid example (14)
Tengo una tabla que se crea usando ng-repeat. Quiero agregar validación a cada elemento en la tabla. El problema es que cada celda de entrada tiene el mismo nombre que la celda de arriba y debajo. Intenté usar el valor de {{$index}}
para nombrar las entradas, pero a pesar de que los literales de cadena en HTML parecen correctos, ahora está funcionando.
Aquí está mi código a partir de ahora:
<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>
He intentado eliminar el {{}}
del índice, pero eso tampoco funciona. A partir de ahora, la propiedad de validación de la entrada funciona correctamente, pero el mensaje de error no se muestra.
¿Alguien tiene alguna sugerencia?
Editar: Además de las excelentes respuestas a continuación, aquí hay un artículo de blog que trata este tema con más detalle: http://www.thebhwgroup.com/blog/2014/08/angularjs-html-form-design-part-2/
AngularJS se basa en los nombres de entrada para exponer los errores de validación.
Desafortunadamente, a partir de hoy, no es posible (sin utilizar una directiva personalizada) generar dinámicamente un nombre de entrada. De hecho, al verificar los documentos de entrada , podemos ver que el atributo de nombre solo acepta una cadena.
Para resolver el problema del ''nombre dinámico'' , necesita crear una forma interna (vea 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>
La otra alternativa sería escribir una directiva personalizada para esto.
Aquí está el jsFiddle que muestra el uso de ngForm: http://jsfiddle.net/pkozlowski_opensource/XK2ZT/2/
Aquí un ejemplo de cómo lo hago, no sé si es la mejor solución, pero funciona perfectamente.
Primero, codifica en HTML. Mira ng-class, está llamando a la función hasError. Mire también la declaración del nombre de la entrada. Uso el índice $ para crear diferentes nombres de entrada.
<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}}"/>
Y ahora, aquí está la función 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);
};
Basándome en la answer de pkozlowski.opensource, he agregado una forma de tener nombres de entrada dinámicos que también funcionan con ngMessages . Observe la parte ng-init
en el elemento ng-form
y el uso de furryName
. furryName
convierte en el nombre de la variable que contiene el valor de la variable para el atributo de name
la input
.
<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>
Desde que se formuló la pregunta, el equipo Angular ha resuelto este problema al permitir la creación dinámica de nombres de entrada.
Con la versión angular 1.3 y posterior , ahora puede hacer esto:
<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 también introdujo ngMessages, una herramienta más poderosa para la validación de formularios. Puede usar la misma técnica con 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>
Es demasiado tarde, pero podría ser que pueda ayudar a cualquier persona
- Crea un nombre único para cada control
- Validar usando
fromname[uniquname].$error
Código de muestra:
<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>
Ver demostración de trabajo aquí
Es posible y así es como hago lo mismo con una tabla de entradas.
envuelva la mesa en una forma como
Entonces solo usa esto
Tengo un formulario con directivas multi-anidadas que contienen entrada (s), selección (es), etc. ... Todos estos elementos están incluidos en ng-repeats y valores de cadena dinámicos.
Esta es la forma de usar la directiva:
<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>
Nota: puede agregar e indexar a la concatenación de cadenas si necesita serializar tal vez una tabla de entradas; que es lo que hice
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
}
});
Esto debería manejar muchas situaciones en las que simplemente no se sabe dónde estará el formulario. ¿O quizás tiene formularios anidados, pero por alguna razón desea adjuntar este nombre de entrada a dos formularios? Bueno, simplemente pase el nombre del formulario al que desea adjuntar el nombre de entrada.
Lo que quería, era una forma de asignar valores dinámicos a las entradas que nunca sabré, y luego simplemente llame a $ scope.myFormName. $ Valid.
Puede agregar cualquier otra cosa que desee: más tablas más entradas de formulario, formularios anidados, lo que quiera. Simplemente pase el nombre del formulario sobre el que desea validar las entradas. Luego, al enviar el formulario, pregunta si $ scope.yourFormName. $ Valid
Esto obtendrá el nombre en ng-repeat para aparecer por separado en la validación del formulario.
<td>
<input ng-model="r.QTY" class="span1" name="{{''QTY'' + $index}}" ng-pattern="/^[/d]*/.?[/d]*$/" required/>
</td>
Pero tuve problemas para que mirase en su mensaje de validación, así que tuve que usar un ng-init para que resolviera una variable como la clave del objeto.
<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>
Mis requisitos eran un poco diferentes de los que me hicieron en la pregunta original, pero espero poder ayudar a alguien que está pasando por el mismo problema que yo.
Tuve que definir si un campo era obligatorio o no basado en una variable de ámbito. Así que, básicamente, tuve que establecer ng-required="myScopeVariable"
(que es una variable booleana).
<div class="align-left" ng-repeat="schema in schemas">
<input type="text" ng-required="schema.Required" />
</div>
Revisando estas soluciones, la que proporcionó Al Johri arriba es la más cercana a mis necesidades, pero su directiva era un poco menos programable de lo que yo quería. Aquí está mi versión de sus soluciones:
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;
}
}
}
}
};
});
Esta solución le permite pasar una expresión del generador de nombres a la directiva y evita el bloqueo a la sustitución de patrones que estaba usando.
También tuve problemas inicialmente con esta solución ya que no mostraba un ejemplo de uso en el marcado, así que aquí es cómo lo usé.
<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>
Tengo un ejemplo de trabajo más completo en github .
Se agregó un ejemplo más complejo con "validación personalizada" en el lado del controlador 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>
Si no quiere usar ng-form, puede usar una directiva personalizada que cambiará el atributo de nombre del formulario. Coloque esta directiva como un atributo en el mismo elemento que su ng-modelo.
Si está utilizando otras directivas conjuntamente, tenga cuidado de que no tengan la propiedad "terminal" configurada, de lo contrario esta función no podrá ejecutarse (dado que tiene una prioridad de -1).
Por ejemplo, al usar esta directiva con ng-options, debe ejecutar esta línea monopatch: 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;
}
};
});
A menudo me resulta útil usar ng-init para establecer el índice $ en un nombre de variable. Por ejemplo:
<fieldset class=''inputs'' ng-repeat="question questions" ng-init="qIndex = $index">
Esto cambia tu expresión regular a:
name = name.replace(//{/{qIndex/}/}/g, scope.qIndex);
Si tiene varias ng-repeticiones anidadas, ahora puede usar estos nombres de variable en lugar de $ parent. $ Index.
Definición de "terminal" y "prioridad" para directivas: https://docs.angularjs.org/api/ng/service/ $ compile # directive-definition-object
Comentario de Github con respecto a la necesidad de monopatch ng-option: https://github.com/angular/angular.js/commit/9ee2cdff44e7d496774b340de816344126c457b3#commitcomment-6832095 https://twitter.com/aljohri/status/482963541520314369
ACTUALIZAR:
También puedes hacer que esto funcione con ng-form.
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;
}
}
}
}
};
});
Si usa ng-repeat $ index funciona así
name="QTY{{$index}}"
y
<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>
tenemos que mostrar el ng-show en ng-pattern
<span class="alert-error" ng-show="form[''QTY'' + $index].$error.pattern">
<span class="alert-error" ng-show="form[''QTY'' + $index].$error.required">
Use la directiva ng-form dentro de la etiqueta en la que está usando la directiva ng-repeat. A continuación, puede usar el alcance creado por la directiva ng-form para hacer referencia a un nombre genérico. Por ejemplo:
<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>
Crédito a: http://www.benlesh.com/2013/03/angular-js-validating-form-elements-in.html
la validación está trabajando con ng repeat si uso la siguiente sintaxis scope.step3Form[''item[107][quantity]''].$touched
No sé si es una mejor práctica o la mejor solución, pero funciona
<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>