Validate form using dynamically textarea name - angularjs

I have one validation form using angular. I would like validate forms in ng-repeat using dynamically textarea name. Look on my code:
<div ng-repeat="comment in comments track by $index">
<form name="main.adminArticleReplyForm" novalidate>
<div ng-class="{'has-success':(main.adminArticleReplyForm.commentReply_{{$index}}.$valid), 'has-error':(!main.adminArticleReplyForm.commentReply_{{$index}}.$valid)}">
<textarea name="commentReply_{{$index}}" ng-model="main.commentReply[$index]" required></textarea>
<ul ng-show="(!main.adminArticleReplyForm.commentReply_{{comment.number}}.$pristine && main.adminArticleReplyForm.$error.minlength[0].$viewValue.length)">
<li>You have to add {{10 - main.adminArticleReplyForm.$error.minlength[0].$viewValue.length}} characters</li>
</ul>
<button type="submit">Submit</button>
</div>
</form>
</div>
And my problems:
If you check {{main.adminArticleReplyForm}} commentReply_{{$index}} is always last $index in ng-repeat for example if are 4 comments in array in form always is commentReply_4, but textareas name in DOM are correct.
I have to use controller as, in my case main, but if I remove main from form name, everything works nice.
Summary, {{main.adminArticleReplyForm}} is validate only for last $index in ng-repeat previously $indexes are ignored
Here is fiddle for look that every commentReply_{{$index}} is commentReply_2 https://jsfiddle.net/8od25zhb/5/

Description
you don't need to track it by index that make repeat last index
you don't need to add main to the form, because form is in repeat so make it unique, if you want to have just one form make it out of repeat; so we just use comment.adminArticleReplyForm as form name, and just main.comments used main from controller.
by this 2 step your forms validate just the textarea which inside it.
you don't need create unique names and models for textarea, because you create them inside ng-repeat and all are unique.
<div ng-app="app" ng-controller="ctrl as main">
<div ng-repeat="comment in main.comments">
<form name="comment.adminArticleReplyForm" novalidate>
{{comment.adminArticleReplyForm}}
<div>
<textarea name="commentReply" ng-model="comment.commentReply" required></textarea>
<button type="submit">Submit</button>
</div>
</form>
<hr/>
</div>
</div>

Since, AngularJS default validation works on hierarchy of element names to detect their state thus, the name of textarea you have used is commentReply_{{$index}} with a underscore but you are using commentReply{{comment.number}} in the validation expressions. So make it look with a underscore and it will work. So, final code will look something like this:
<div ng-repeat="comment in comments track by $index">
<form name="main.adminArticleReplyForm" novalidate>
<div ng-class="{'has-success':(main.adminArticleReplyForm.commentReply_{{$index}}.$valid), 'has-error':(!main.adminArticleReplyForm.commentReply_{{$index}}.$valid)}">
<textarea name="commentReply_{{$index}}" ng-model="main.commentReply[$index]" required></textarea>
<ul ng-show="(!main.adminArticleReplyForm.commentReply_{{comment.number}}.$pristine && main.adminArticleReplyForm.$error.minlength[0].$viewValue.length)">
<li>You have to add {{10 - main.adminArticleReplyForm.$error.minlength[0].$viewValue.length}} characters</li>
</ul>
<button type="submit">Submit</button>
</div>
</form>
</div>

Related

Angular ng-show is not executing with ng-message tag

Not sure what I am doing wrong here.
I want to display an ng-message after the user submit the form. However , the message is shown when the form is rendered. It seem like the ng-show is not evaluating.
I printed the field in the expression and it is false when I open the form. Also, I change it to ng-hide but I have the same issue.
Can you please have a look ..
<div class="small-12">
<label>first name
<span class="field-error"> *</span>
</label>
<input name="firstName"
type="text"
maxlength="25"
ng-disabled="isSubmitting" required
ng-model="candidate.firstName"
ng-class="{error:isFormSubmitted && contactForm.firstName.$error.required}" />
<div ng-messages="forms.contactForm.firstName.$error"
class="errorFormLabel" role='alert'>
<div ng-message="required"
ng-show="isFormSubmitted">This is a required field {{isFormSubmitted}}
</div>
</div>
</div>
Think about ng-messages as a switch clause, you can nest the /ng-show/ng-hide inside ng-message directive but not use them together
For example:
<div ng-messages="forms.contactForm.firstName.$error"
class="errorFormLabel" role='alert'>
<div ng-message="required">
<span ng-show="isFormSubmitted">This is a required field {{isFormSubmitted}}</span>
</div>
</div>
</div>
If you don't want an extra element: <span>, try ng-if, basically ng-if has the priority higher than any other angularjs directive and it will force the ng-message directive to be removed from the DOM tree if the expression is not positive.
<div ng-message="required"
ng-if="isFormSubmitted">This is a required field {{isFormSubmitted}}
</div>

Can ng-model replace Ids?

Is there a way to avoid IDs while creating elements i.e. using ng-model it is easier to validate form fields and other stuff?
<div ng-repeat="x in TestData">
<input type="text" id="nameField-{{$index}}"/>
</div>
The above code will display the number of input boxes equal to the length of the TestData array where every text box will have a unique id "nameField-0", "nameField-1" and so on, using these IDs we can take the textbox values, validate the fields etc.
<div ng-repeat="x in TestData">
<input type="text" ng-model=""/> <!-- what shall be done here to have unique IDs -->
</div>
Although going with IDs is working fine for me but as far as I can understand If I use IDs then that will not be a pure angular way (or IDs cannot be replaced by mg-models), correct me If I am wrong.
You don't need to grab element with id/name/etc. You can use ng-model to bind your values and ng-pattern to validate them.
<div ng-repeat="x in testData">
<ng-form name="tForm">
<input type="text" name="tInput" ng-model="x.name" ng-pattern="/^[A-Z]*$/" />
<span ng-if="tForm.tInput.$error.pattern" style="color:red">Invalid!</span>
</ng-form>
</div>
See this jsbin.
In your controller add this,
$scope.nameField = [];
in ng-reapeat add this,
<div ng-repeat="x in TestData">
<input type="text" ng-model="nameField[$index]"/>
</div>
nameField will be an array with all your values.
<div ng-repeat="x in TestData" ng-init='this["nameField-"+x]'>
<input ng-model='this["nameField-"+x]'/>
</div>
You use ng-init to add new variables to the controller, "this" refer to your scope, and since the scope is an object, since this[bla] == this.bla tou are actually saying, add to my scope a variable name "nameField=" and you add the x value.
after you have created your variable, just use it in the same way in your ng-model.
If you want to show your variable's value in any div/span, just use it via ng-bind:
<div ng-bind='this["nameField-"+x]'></div>

How to repeat duplicate objects using ng-repeat in AngularJs?

This is my code.
$scope.data=[];
$scope.data=[{"label":"name","type":"string"},{"label":"email","type":"string"}];
$scope.addFields = function (field) {
$scope.data.push(field);
};
This is my html:-
<div ng-repeat="eachItem in data">
<input type="button" value="add" ng-click="addFields(eachItem)"/>
<label>{{eachItem.label}}</label>
<input type="text" ng-model="fieldValue"/>
</div>
when i click add button push one more object into $scope.data array like
$scope.data=[{"label":"name","type":"string"},{"label":"email","type":"string"},{"label":"name","type":"string"}];
In the above i got an error
angular.min.js:102 Error: [ngRepeat:dupes] http://errors.angularjs.org/1.3.14/ngRepeat/dupes?p0=nestedField%20in%20fieā€¦%2C%22type%22%3A%22string%22%2C%22%24%24hashKey%22%3A%22object%3A355%22%7D
at Error (native)
I have duplicate objects after adding. because i want to repeat label names using ng-repeat in angularjs.First i have output like this
OutPut:-
name textbox
email textbox
After add button click Output:-
name textbox
email textbox
name textbox
use track by $index
var app = angular.module("app",[])
app.controller('ctrl',['$scope', function($scope){
$scope.data=[];
$scope.data=[{"label":"name","type":"string"},{"label":"email","type":"string"}];
$scope.addFields = function (field) {
$scope.data.push(field);
};
}]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.22/angular.min.js"></script>
<div ng-app="app" ng-controller="ctrl">
<div class="item item-checkbox">
<div ng-repeat="eachItem in data track by $index">
<input type="button" value="add" ng-click="addFields(eachItem)"/>
<label>{{eachItem.label}}</label>
<input type="text" />
</div>
</div>
Use track by for this purpose.
<div ng-repeat="eachItem in data track by $index">
<input type="button" value="add" ng-click="addFields(eachItem)"/>
<label>{{eachItem.label}}</label>
<input type="text" ng-model="eachItem.value" />
</div>
You also able to use track by with your custom filed, like id, or whatever
Important: It's better to use track by in each ng-repeat, cause it's improve ng-repeat's performance (read more).
But avoid to use track by in ng-options and other cases when you use select as .. for ... construction (read more)
JsFiddle here
You have to ensure that items in the array have an unique key. If that is not possible you can use track by $index in the ng-repeat.
Check the details here

Angular - Form and $pristine does it work with nested inputs?

I have the following form.
A couple of issues I am facing:
1 - I'm unable to check for $pristine on the form with console.log($scope.myFirstForm.$pristine); I'm seeing the error: `cannot read property '$prestine' of undefined'.
Based on my code is this actually possible i.e. to use when the input fields are nested within several div tags?
2 - To clear the fields I'm currently having to do each field individually, is it possible to do at the form level which clears all input fields with that?
<form name="myFirstForm">
<div class="row">
<div class="large-12 medium-12 small-12 columns">
<input ng-model="main.pToAdd" type="text"/>
</div>
</div>
<div class="row homeTopBackgroundColor">
<div class="large-12 medium-12 small-12 columns">
<h5>Room</h5>
</div>
<div class="row large-12 medium-12 small-12 columns">
<input ng-model="main.o1ToAdd" type="text" />
</div>
</div>
<div class="row">
<div class="large-12 medium-12 small-12 columns">
<input ng-model="main.opToAdd" type="text"/>
</div>
</div>
</form>
SOLVED:
In my routes file I was specifying a controller (so 2 controllers on one page). I was checking on the child scope not the parent hence not being found. So I removed the setting of a controller in the routes file and just specified it in required places of HTML.
First the form included in the controller will automatically be in the scope of that controller.
Inputs inclosed in the form are part of the form no matter how nested it is. (use the name attr to be able to validate them.. assign errors and states to them)
Setting the form to pristine state will clear even the validations .. setting the main object in your case to {} will clear all the input fields.
`cannot read property '$prestine' of undefined -> can be because you are calling it in the controller before the form is assigned in the view. try the same thing on a click of any button should not give the same answer.

ng-model and form inside ng-repeat

I am creating a form for each item in my $scope. ng-model is not linking with the data on my form submit.
<li ng-repeat="item in favourites">
<form ng-submit="DeleteFavourite()" class="form-horizontal" id="frmSpec">
<input ng-model="item.Description"/>
<input ng-model="item.Refno"/>
<button type="submit"class="btn">{{item.Description}}
<span class="glyphicon glyphicon-remove"></span>
</button>
</form>
</li>
The issue is very closely related to the comment by #DavidBeech . In an angular controller the scope is seen as a hierarchy object.
So for example if you have the following:
<div ng-controller="SomeCtrl">
<li ng-repeat="item in favourites">
<form ng-submit="DeleteFavourite()" class="form-horizonatal" id="frmSpec">
<input ng-model="item.Description"/>
<input ng-model="item.Refno"/>
<button type="submit"class="btn">{{item.Description}}
<span class="glyphicon glyphicon-remove"></span>
</button>
</form>
</li>
</div>
When that controller is injected into the div and that new scope instance is created it only sees what is at that level and the scopes of it's parents. It has no knowledge of its children's scopes. Therefore, when you call DeleteFavourite() since it is a method attached to the scope of the controller it will not have the context of the ng repeat. So as David stated you will need to do something like DeleteFavorite(item) in order for it to have knowledge of what you are submitting otherwise you will not have knowledge of what item in the iteration you are submitting.
Feel free to comment if you want an example and I can put together a fiddle with an example of scope inheritance.

Resources