Angular Directive not displaying a label - javascript

I'm making a directive for a textfield in Angular. I have successfully gotten the textfield to show up and work. Based on whats inside inside the textfield I have a message displaying. For some reason the message wont show up.
Here is my initial code that I'm trying to convert into a directive:
<form name="objectiveForm">
<p ng-if="requireObjectiveToBeSpecified"
ng-class="{'has-error': (objectiveForm.objectiveInput.$touched || objectiveHasBeenPreviouslyEdited) && explorationObjectiveService.displayed.length < 15}">
Add a goal: what does the exploration help people to do?
<input name="objectiveInput" type="text" class="form-control" ng-model="explorationObjectiveService.displayed"
placeholder="Learn how to ...">
<span class="help-block"
ng-show="(objectiveForm.objectiveInput.$touched || objectiveHasBeenPreviouslyEdited) && explorationObjectiveService.displayed.length < 15" style="font-size: smaller; color: #a94442">Please provide a complete sentence that describes what this exploration is about.</span>
</p>
</form>
This is the code I'm replacing it with:
<exploration-objective-editor ng-if="requireObjectiveToBeSpecified"
label-text="Add a goal: what does the exploration help people to do?"
form-style="{'font-weight': 'normal'}">
</exploration-objective-editor>
Here is my HTML code for the directive, file name = exploration_objective_editor_directive.html:
<form name="objectiveForm">
<p ng-class="{'has-error': (objectiveForm.objectiveInput.$touched || objectiveHasBeenPreviouslyEdited) && explorationObjectiveService.displayed.length < 15}">
<label for="explorationObjective" ng-style="<[::formStyle]>"><[::labelText]></label>
<input id="explorationObjective" type="text"
class="form-control protractor-test-exploration-objective-input"
ng-model="explorationObjectiveService.displayed"
enter code hereng-blur="onInputFieldBlur()" placeholder="Learn how to..." ng-trim="false">
<span class="help-block" ng-show="(objectiveForm.objectiveInput.$touched || objectiveHasBeenPreviouslyEdited) && explorationObjectiveService.displayed.length < 15" style="font-size: smaller">
<em>In a complete sentence, tell people what they'll learn from this exploration.</em>
<span ng-if="explorationObjectiveService.displayed.length > 0">
<em>Characters used: <[explorationObjectiveService.displayed.length]>/100</em>
</span>
</span>
</p>
Here is my JS code for the directive, file name = ExplorationObjectiveEditorDirective.js:
oppia.directive('explorationObjectiveEditor', [
'UrlInterpolationService', function(UrlInterpolationService) {
return {
restrict: 'E',
scope: {
// The text for the label of the field.
labelText: '#',
// Additional CSS style to define the width and font-weight.
formStyle: '#',
// The method to call when the input field is blurred.
onInputFieldBlur: '&'
},
templateUrl: UrlInterpolationService.getDirectiveTemplateUrl(
'/pages/exploration_editor/' +
'exploration_objective_editor_directive.html'),
controller: [
'$scope', 'explorationObjectiveService',
function ($scope, explorationObjectiveService) {
$scope.explorationObjectiveService = explorationObjectiveService;
}
]
}
}
]);
This is what I want it to look like:
This is what it looks like right now:

Related

AngularJS Directive Create OnTheFly Textbox and Validation

In a directive, I am creating a textbox on-the-fly and the validation along with it.
The texbox is being created correctly and works.
See plnker:
https://plnkr.co/edit/YJIwS8bD0w5e9LbWBtlJ?p=preview
BUT the error div ng-show is not being generated corretly from the directive's propErrPattern: '=' AND propErrReqr: '='.
I have tried at least a dozen ways but nothing works. The template is generating:
<p class="error" ng-show="propErrPattern || propErrReqr">
<b class="ng-binding">Required. Length:- Characters:</b> Letters, Numbers, Dash, Underscore, Space
</p>
<!-- instead of generating -->
<!-- instead of generating -->
<p class="error" ng-show="FirstName.$error.pattern || irstName.$error.required">
app.directive('textBox', function () {
return {
scope: {
label: '#label',
value: '#value',
property: '=',
propErrPattern: '=',
propErrReqr: '=',
lenMin: '#lenMin',
lenMax: '#lenMax',
},
restrict: 'E',
replace: true,
templateUrl: 'textboxTemplate.html'
};
});
In a form I call the directive by:
<text-box
label="First Name"
value="FirstName"
property='FirstName'
propErrPattern='FirstName.$error.pattern'
propErrReqr='FirstName.$error.required'
lenMin='3'
lenMax='15'>
</text-box>
The textboxTemplate.html (notice ng-show="propErrPattern || propErrReqr")
<div class="form-group">
<label for="{{value}}" class="control-label col-sm-4">{{label}}</label>
<div class="col-sm-8">
<input type="text"
class="form-control"
id="{{value}}"
name="{{value}}"
ng-model="property"
ng-pattern="/^[a-z\d\-_\s]+$/i"
ng-minlength="{{lenMin}}"
ng-maxlength="{{lenMax}}"
ng-trim="true"
required />
<!-- NOT GENERATING CORRECTLY -->
<!-- NOT GENERATING CORRECTLY -->
<p class="error" ng-show="propErrPattern || propErrReqr" >
<b>Required. Length:{{lenMin}}-{{lenMax}} Characters:</b> Letters, Numbers, Dash, Underscore, Space
</p>
</div>
</div>
So there are a number of things wrong with your implementation. Your use of the directive should look like this.
<text-Box label="First Name" value="FirstName" property='FirstName'
prop-err-pattern='myForm.FirstName.$error.pattern'
prop-err-reqr='myForm.FirstName.$error.required' lenMin='3' lenMax='15'>
</text-Box>
Notice that the properties that are camel case in your directive should are set via a dashed '-' name. That is propErrPattern becomes prop-err-pattern.
next note that your values for propErrPattern and propErrReqr should start with the name of your form, in this case 'myForm'.
Those two changes should make things work properly.

Adding custom validation to angular textarea

I want to add custom validation to the angular form.
<div class="form-group" ng-class="{ 'has-error': contactform.inputAmount.$invalid && submitted }">
<label for="inputAmount" class="col-lg-2 control-label">Amount</label>
<div class="col-lg-10">
<textarea ng-model="formData.inputAmount" class="validate[required,custom[comment]] feedback-input" rows="4" id="comment" name="comment" placeholder="Enter Amount to Convert" required></textarea>
</div>
</div>
I want only number or decimal input with inverted commas like
"1"
"123.1"
"123.12221"
How can i add this pattern
Help would be highly appreciated.
Thanks
You can use ng-pattern to validate your textarea.
Checkout here for example.
http://www.java2s.com/Tutorials/AngularJS/AngularJS_Example/Directives/Use_ng_pattern_to_validate.htm
Refer here for dynamic ng-pattern validation Angularjs dynamic ng-pattern validation
Use Directive for custom validations.
First write custom directive:
//Check For FLoat Validation
adminApp.directive('validateFloat', function() {
var FLOAT_REGEXP = /^\-?\d+((\.)\d+)?$/;
return {
require: 'ngModel',
restrict: '',
link: function(scope, elm, attrs, ctrl) {
// only apply the validator if ngModel is present and Angular has added the Float Number validator
ctrl.$validators.validateFloat = function(modelValue) {
return ctrl.$isEmpty(modelValue) || FLOAT_REGEXP.test(modelValue);
};
}
};
});
In directive used Regex for float (or) Decimal to check with ngModel value.
Then use directive in html page.
<input type="text" name="amount" class="form-control" ng-model=test.amount" validate-float required>
<span class="error-container" ng-show="myForm.amount.$dirty && myForm.amount.$invalid">
<span ng-cloak style="color:red" ng-show="myForm.amount.$error.required">Please Enter amount</span>
<span ng-cloak style="color:red" ng-show="!myForm.amount.$error.required&&myForm.amount.$error.validateFloat">Enter valid amount</span>
</span>
In HTML page mention the custom directive "validate-float" as attribute. And for showing error message in ng-Show mention the $error.validateFloat.
For more info about directive and validations: https://docs.angularjs.org/guide/directive,
https://docs.angularjs.org/guide/forms

AngularJS - form wrapper directive, for validation purpose

Consider the form:
<form class="form" name="myForm" ng-submit="submit()">
<div class="form-group" ng-class="{'has-error': myForm.title.$invalid}">
<label for="title">Title: </label>
<input id="title" name="title" class="form-control" ng-model="title"
required>
<div ng-messages="myForm.title.$dirty && myForm.title.$error" role="alert">
<div ng-message="required" class="alert small with-error-font">
<span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span>
Title is required!
</div>
</div>
</div>
</form>
I want to achieve the following behaviour:
Validation error message should appear if user started typing and left the field empty (it's not pristine anymore). Otherwise, it shouldn't be visible till user clicks submit.
So I thought it could be done by using sort of 'aspect'/'interceptor' which simply checks if form is valid. If it is, then it should invoke ng-submit function. If not, then it should invoke $setDirty on each field (so that validation errors appear) and simply return.
I want to achieve it by creating ma own directive, like the following:
let validateAndSubmit = function (onValidationSuccessCallback) {
return function () {
// this is where validation logic should happen
onValidationSuccessCallback();
};
};
let ValidatingFormDirective = function () {
return {
restrict: 'E',
transclude: true,
replace: true,
scope: {
name: '#',
postValidationCallback: '&'
},
link: function (scope) {
scope.validateAndSubmit = validateAndSubmit(scope.postValidationCallback);
},
templateUrl: 'app/events/common/forms/validating-form.template.html'
}
};
export default ValidatingFormDirective;
and here's my directive template:
<form name="{{name}}" novalidate ng-submit="validateAndSubmit()" ng-transclude></form>
Problem:
Now if I change form to validation-form in my html, it seems like myForm is not recognized at all, because neither ngMessages block nor ng-class appears.
How can I fix it? Or maybe should I try completely different approach?

Dynamic input name attribute in angularjs ng-repeat [duplicate]

I have a table that is created using ng-repeat. I want to add validation to each element in the table. The problem is that each input cell has the same name as the cell above and below it. I attempted to use the {{$index}} value to name the inputs, but despite the string literals in HTML appearing correct, it is now working.
Here is my code as of now:
<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>
I have tried removing the {{}} from index, but that does not work either. As of now, the validation property of the input is working correctly, but the error message is not displayed.
Anyone have any suggestions?
Edit: In addition to the great answers below, here is a blog article that covers this issue in more detail: http://www.thebhwgroup.com/blog/2014/08/angularjs-html-form-design-part-2/
Since the question was asked the Angular team has solved this issue by making it possible to dynamically create input names.
With Angular version 1.3 and later you can now do this:
<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>
Demo
Angular 1.3 also introduced ngMessages, a more powerful tool for form validation. You can use the same technique with 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>
AngularJS relies on input names to expose validation errors.
Unfortunately, as of today, it is not possible (without using a custom directive) to dynamically generate a name of an input. Indeed, checking input docs we can see that the name attribute accepts a string only.
To solve the 'dynamic name' problem you need to create an inner form (see 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>
The other alternative would be to write a custom directive for this.
Here is the jsFiddle showing the usage of the ngForm: http://jsfiddle.net/pkozlowski_opensource/XK2ZT/2/
If you don't want to use ng-form you can use a custom directive that will change the form's name attribute. Place this directive as an attribute on the same element as your ng-model.
If you're using other directives in conjunction, be careful that they don't have the "terminal" property set otherwise this function won't be able to run (given that it has a priority of -1).
For example, when using this directive with ng-options, you must run this one line 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;
}
};
});
I often find it useful to use ng-init to set the $index to a variable name. For example:
<fieldset class='inputs' ng-repeat="question questions" ng-init="qIndex = $index">
This changes your regular expression to:
name = name.replace(/\{\{qIndex\}\}/g, scope.qIndex);
If you have multiple nested ng-repeats, you can now use these variable names instead of $parent.$index.
Definition of "terminal" and "priority" for directives: https://docs.angularjs.org/api/ng/service/$compile#directive-definition-object
Github Comment regarding need for ng-option monkeypatch:
https://github.com/angular/angular.js/commit/9ee2cdff44e7d496774b340de816344126c457b3#commitcomment-6832095
https://twitter.com/aljohri/status/482963541520314369
UPDATE:
You can also make this work with 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;
}
}
}
}
};
});
Use the ng-form directive inside of the tag in which you are using the ng-repeat directive. You can then use the scope created by the ng-form directive to reference a generic name. For example:
<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>
Credit to: http://www.benlesh.com/2013/03/angular-js-validating-form-elements-in.html
Added more complex example with "custom validation" on the side of controller 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>
Looking over these solutions, the one provided by Al Johri above is the closest to my needs, but his directive was a little less programmable then I wanted. Here is my version of his solutions:
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;
}
}
}
}
};
});
This solution lets you just pass a name generator expression to the directive and avoids the lock down to pattern substitution he was using.
I also had trouble initially with this solution since it didn't show an example of using it in markup, so here is how I used it.
<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>
I have a more complete working example on github.
validation is working with ng repeat if I use the following syntax scope.step3Form['item[107][quantity]'].$touched
I don't know it's a best practice or the best solution, but it works
<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>
Building on pkozlowski.opensource's answer, I've added a way to have dynamic input names that also work with ngMessages. Note the ng-init part on the ng-form element and the use of furryName. furryName becomes the variable name that contains the variable value for the input's name attribute.
<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>
Here an example of how I do that, I don't know if it is the best solution, but works perfectly.
First, code in HTML.
Look at ng-class, it's calling hasError function.
Look also to the input's name declaration. I use the $index to create different input names.
<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}}"/>
And now, here is the hasError function:
$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);
};
It is too late but might be it can help anyone
Create unique name for every control
Validate by using fromname[uniquname].$error
Sample code:
<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>
See working demo here
If your using ng-repeat $index works like this
name="QTY{{$index}}"
and
<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>
we have to show the ng-show in ng-pattern
<span class="alert-error" ng-show="form['QTY' + $index].$error.pattern">
<span class="alert-error" ng-show="form['QTY' + $index].$error.required">
It is possible and here is how I do the same thing with a table of inputs.
wrap the table in a form like so
Then just use this
I have a form with multi-nested directives that all contain input(s), select(s), etc...
These elements are all enclosed in ng-repeats, and dynamic string values.
This is how to use the directive:
<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>
Note: you can add and index to the string concatenation if you need to serialize perhaps a table of inputs; which is what I did.
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
}
});
This should handle many situations where you just don't know where the form will be. Or perhaps you have nested forms, but for some reason you want to attach this input name to two forms up? Well, just pass in the form name you want to attach the input name to.
What I wanted, was a way to assign dynamic values to inputs that I will never know, and then just call $scope.myFormName.$valid.
You can add anything else you wish: more tables more form inputs, nested forms, whatever you want. Just pass the form name you want to validate the inputs against. Then on form submit ask if the $scope.yourFormName.$valid
This will get the name in the ng-repeat to come up seperate in the form validation.
<td>
<input ng-model="r.QTY" class="span1" name="{{'QTY' + $index}}" ng-pattern="/^[\d]*\.?[\d]*$/" required/>
</td>
But I had trouble getting it to look up in its validation message so I had to use an ng-init to get it to resolve a variable as the object key.
<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>
My requirements were a bit different than the ones asked on the original question, but hopefully I might help someone who is going through the same problem that I was..
I had to define if a field was required or not based on a scope variable.. So I basically had to set ng-required="myScopeVariable" (which is a boolean variable).
<div class="align-left" ng-repeat="schema in schemas">
<input type="text" ng-required="schema.Required" />
</div>

How to validate inputs dynamically created using ng-repeat, ng-show (angular)

I have a table that is created using ng-repeat. I want to add validation to each element in the table. The problem is that each input cell has the same name as the cell above and below it. I attempted to use the {{$index}} value to name the inputs, but despite the string literals in HTML appearing correct, it is now working.
Here is my code as of now:
<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>
I have tried removing the {{}} from index, but that does not work either. As of now, the validation property of the input is working correctly, but the error message is not displayed.
Anyone have any suggestions?
Edit: In addition to the great answers below, here is a blog article that covers this issue in more detail: http://www.thebhwgroup.com/blog/2014/08/angularjs-html-form-design-part-2/
Since the question was asked the Angular team has solved this issue by making it possible to dynamically create input names.
With Angular version 1.3 and later you can now do this:
<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>
Demo
Angular 1.3 also introduced ngMessages, a more powerful tool for form validation. You can use the same technique with 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>
AngularJS relies on input names to expose validation errors.
Unfortunately, as of today, it is not possible (without using a custom directive) to dynamically generate a name of an input. Indeed, checking input docs we can see that the name attribute accepts a string only.
To solve the 'dynamic name' problem you need to create an inner form (see 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>
The other alternative would be to write a custom directive for this.
Here is the jsFiddle showing the usage of the ngForm: http://jsfiddle.net/pkozlowski_opensource/XK2ZT/2/
If you don't want to use ng-form you can use a custom directive that will change the form's name attribute. Place this directive as an attribute on the same element as your ng-model.
If you're using other directives in conjunction, be careful that they don't have the "terminal" property set otherwise this function won't be able to run (given that it has a priority of -1).
For example, when using this directive with ng-options, you must run this one line 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;
}
};
});
I often find it useful to use ng-init to set the $index to a variable name. For example:
<fieldset class='inputs' ng-repeat="question questions" ng-init="qIndex = $index">
This changes your regular expression to:
name = name.replace(/\{\{qIndex\}\}/g, scope.qIndex);
If you have multiple nested ng-repeats, you can now use these variable names instead of $parent.$index.
Definition of "terminal" and "priority" for directives: https://docs.angularjs.org/api/ng/service/$compile#directive-definition-object
Github Comment regarding need for ng-option monkeypatch:
https://github.com/angular/angular.js/commit/9ee2cdff44e7d496774b340de816344126c457b3#commitcomment-6832095
https://twitter.com/aljohri/status/482963541520314369
UPDATE:
You can also make this work with 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;
}
}
}
}
};
});
Use the ng-form directive inside of the tag in which you are using the ng-repeat directive. You can then use the scope created by the ng-form directive to reference a generic name. For example:
<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>
Credit to: http://www.benlesh.com/2013/03/angular-js-validating-form-elements-in.html
Added more complex example with "custom validation" on the side of controller 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>
Looking over these solutions, the one provided by Al Johri above is the closest to my needs, but his directive was a little less programmable then I wanted. Here is my version of his solutions:
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;
}
}
}
}
};
});
This solution lets you just pass a name generator expression to the directive and avoids the lock down to pattern substitution he was using.
I also had trouble initially with this solution since it didn't show an example of using it in markup, so here is how I used it.
<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>
I have a more complete working example on github.
validation is working with ng repeat if I use the following syntax scope.step3Form['item[107][quantity]'].$touched
I don't know it's a best practice or the best solution, but it works
<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>
Building on pkozlowski.opensource's answer, I've added a way to have dynamic input names that also work with ngMessages. Note the ng-init part on the ng-form element and the use of furryName. furryName becomes the variable name that contains the variable value for the input's name attribute.
<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>
Here an example of how I do that, I don't know if it is the best solution, but works perfectly.
First, code in HTML.
Look at ng-class, it's calling hasError function.
Look also to the input's name declaration. I use the $index to create different input names.
<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}}"/>
And now, here is the hasError function:
$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);
};
It is too late but might be it can help anyone
Create unique name for every control
Validate by using fromname[uniquname].$error
Sample code:
<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>
See working demo here
If your using ng-repeat $index works like this
name="QTY{{$index}}"
and
<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>
we have to show the ng-show in ng-pattern
<span class="alert-error" ng-show="form['QTY' + $index].$error.pattern">
<span class="alert-error" ng-show="form['QTY' + $index].$error.required">
It is possible and here is how I do the same thing with a table of inputs.
wrap the table in a form like so
Then just use this
I have a form with multi-nested directives that all contain input(s), select(s), etc...
These elements are all enclosed in ng-repeats, and dynamic string values.
This is how to use the directive:
<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>
Note: you can add and index to the string concatenation if you need to serialize perhaps a table of inputs; which is what I did.
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
}
});
This should handle many situations where you just don't know where the form will be. Or perhaps you have nested forms, but for some reason you want to attach this input name to two forms up? Well, just pass in the form name you want to attach the input name to.
What I wanted, was a way to assign dynamic values to inputs that I will never know, and then just call $scope.myFormName.$valid.
You can add anything else you wish: more tables more form inputs, nested forms, whatever you want. Just pass the form name you want to validate the inputs against. Then on form submit ask if the $scope.yourFormName.$valid
This will get the name in the ng-repeat to come up seperate in the form validation.
<td>
<input ng-model="r.QTY" class="span1" name="{{'QTY' + $index}}" ng-pattern="/^[\d]*\.?[\d]*$/" required/>
</td>
But I had trouble getting it to look up in its validation message so I had to use an ng-init to get it to resolve a variable as the object key.
<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>
My requirements were a bit different than the ones asked on the original question, but hopefully I might help someone who is going through the same problem that I was..
I had to define if a field was required or not based on a scope variable.. So I basically had to set ng-required="myScopeVariable" (which is a boolean variable).
<div class="align-left" ng-repeat="schema in schemas">
<input type="text" ng-required="schema.Required" />
</div>

Categories

Resources