Creating dynamic forms with angularjs - angularjs

So Im creating dynamic forms with angularjs. And when the form is sent, I delete it from the array. But for some odd reason forms validation rules somehow "stick" to the next form. For example. I send the first form, The second form now gets validated if the third form has valid answers, and so on if the 4th form has valid answers the 3th form will be valid. What could be the possible reasons for this?
This is pretty much striped down code to the basics of what I have
<div ng-repeat="item in ItemsAdder.items track by $index" ng-form="item.itemForm">
<div class="form-group control-group">
<label for="category" class="col-sm-2 control-label">{{trans('adsAdd.category')}}</label>
<select ng-options="category.name for category in categories track by category.id" ng-init="item.category=categories[0]" ng-model="item.category"></select>
</div>
<div class="form-group control-group" ng-class="{ 'has-error' : item.itemForm.price.$invalid && !item.itemForm.price.$pristine }">
<label for="price" class="col-sm-2 control-label">Price</label>
<input ng-model="item.price" ng-class="{ 'has-error' : item.itemForm.price.$invalid && !item.itemForm.price.$pristine }" required type="number" ng-trim="false" name="price">
<p ng-show="item.itemForm.price.$error.number && !item.itemForm.price.$pristine" class="help-block">{{trans('items.add.priceNeedsToBeNumber')}}</p>
<p ng-show="item.itemForm.price.$error.required && !item.itemForm.price.$pristine" class="help-block">{{trans('items.add.priceNeeded')}}</p>
</div>
<div class="form-group control-group" ng-class="{ 'has-error' : item.itemForm.description.$invalid && !item.itemForm.description.$pristine }">
<label for="description" class="col-sm-2 control-label inputLabel">Description</label>
<textarea ng-minlength="3" ng-class="{ 'has-error' : item.itemForm.description.$invalid && !item.itemForm.description.$pristine }" ng-model="item.description" name="description" required class="inputInput" style="max-width: 100%;"></textarea>
<p ng-show="item.itemForm.description.$error.required && !item.itemForm.description.$pristine" class="help-block">{{trans('items.add.descriptionNeeded')}}</p>
</div>
<button ng-click="ItemsAdder.send($index)" ng-disabled="item.itemForm.$invalid">{{trans('adsAdd.send')}}</button>
</div>
And my send function:
ItemsAdderFactory.prototype.send = function ($index) {
var self = this;
var responsePromise = $http.post("",this.items[$index]);
responsePromise.success(function (data, status, headers, config) {
self.items.splice($index, 1);
});
responsePromise.error(function (data, status, headers, config) {
alert('There was an error, please try again.');
});
};
Btw I have the ng-form="" as item.ItemForm so I could access the form through items when a Send All Button is pressed, and it checks what forms are valid and only sends those. If there Is a different or a propper way of doing it, Im all ears.

So guys, I found out the answer. And I'm going to leave it here if anyone else gets this.
The problem was because I was using track by $index in the ng-repeat and I guess the form validation wanted to stick around because the index didn't change.
So don't use track by $index in ng-repeat if your having these problems.

When you delete the submitted from the array ItemsAdder.items remember that the ng-repeat has two way binding so it resets the array and now the 1st index item becomes the 0th index item.
This is best guess about the problem you are facing.

Related

Angular - Binding data to dropdownlist never clears out previous items on updating (keeps old items plus add new)

Hopefully a simple Angular binding question.
I have a TextBox on my Angular 4 [x.component.html] that has an (input) event tied to it, so each time the user types in a character into the textbox it reaches out to a method in the [x.component.ts] typescript file - which then retrieves data from a service to populate a local array of data in the typescript file - which is bound to a dropdownlist on the [x.component.html]. This process functionally works fine.
However the problem I am having is that when it updates the data in the local array, it is not clearing out the previous data in the dropdown, so the dropdown list ends up getting populated with lots of duplicate data. Has anyone experienced this same problem before? The local array of items seems to have the correct number of items in it, it just seems to keep populating the dropdownlist with the items without ever clearing the previous.
Thanks so much for your help.
Please note I have cut the code down to only the relevant parts for brevity.
[x.component.ts]
export class CreateComponent {
testService: TestService;
valueList: testValue[];
constructor(testService: TestService) {
this.testService = TestService;
}
ngOnInit() {
}
FieldChanged(event: any, controlNameToPopulate: string){
if (event.target.value.length >= 9) {
this.testService.getRecords(event.target.value).subscribe((data: any[]) => {
if (data.length != 0){
document.getElementById(controlNameToPopulate).hidden = false;
//this.valueList.slice();
this.valueList = data;
}
});
}
}
}
[x.component.html]
<div class="row">
<div class="col-md-6">
<nb-card>
<nb-card-header>Testing with Updating dropdownlist</nb-card-header>
<nb-card-body>
<form [formGroup]="formGrp">
<div class="form-group row">
<label class="col-sm-3 col-form-label">Quick search</label>
<div class="col-sm-9 input-group">
<input type="text" class="form-control input-sm" style="width: 50%" id="txtbx_ValTest" formControlName="testVal" (change)="FieldChanged($event, 'ddValuesToUpdate')">
<select id="ddValuesToUpdate" class="btn btn-secondary dropdown-toggle" style="width: 50%" hidden>
<option *ngFor="let val of valueList" [value]="val.id">{{val.id}}</option>
</select>
</div>
</div>
<div class="form-group row">
<div class="offset-sm-3 col-sm-9">
<button type="submit" class="btn btn-success pull-right">Add</button>
</div>
</div>
</form>
</nb-card-body>
</nb-card>
</div>
</div>

Angular Form Validation inside ngRepeat

I have a series of table rows with various inputs inside of an ng-repeat and I'm trying to use angular's built in form validation to handle required fields. Everything works well except for when I delete one of the items in the array that is backing the ng-repeat.
Here is how I've set up my validation
<td ng-class="{ 'has-error' : vm.testForm.itemName{{$index}}.$invalid }">
<input type="text"
name="itemName{{$index}}"
class="form-control"
ng-model="item.name"
required />
<div class="help-block"
ng-show="vm.testForm.itemName{{$index}}.$invalid"
ng-messages="vm.testForm['itemName'+$index].$error">
<span ng-message="required">Name is required</span>
</div>
</td>
Plunker showing my issue
https://plnkr.co/edit/Q1qyqlSG69EXR2lTrsHk
In the plunker, if you delete the first table row, you'll notice that while the last row is still invalid, the errors have become attached to the row above. Then as you fill in the last row to make it valid and then empty it again, the errors still show up on the wrong row. Deleting subsequent rows just moves the errors further from the actual invalid row until they don't appear on the table at all.
Is there another way I should be going about validating these inputs?
The problem is that you you don't usally use {{}} (interpolation) in built in angular directives. So change your code to:
<td ng-class="{ 'has-error' : vm.testForm['itemName' + $index].$invalid }">
<input type="text"
name="itemName{{$index}}"
class="form-control"
ng-model="item.name"
required />
<div class="help-block"
ng-show="vm.testForm['itemName' + $index].$invalid"
ng-messages="vm.testForm['itemName'+$index].$error">
<span ng-message="required">Name is required</span>
</div>
</td>
Change:
vm.testForm.itemName{{$index}}.$invalid
To:
vm.testForm['itemName' + $index].$invalid
Made a plunker to verify:
https://plnkr.co/edit/vuQiH4D8qUY9jVoThnCy?p=preview

Select at least one checkbox validation

I have the array of checkboxes from which I want to select at least one checkbox and want to display the validation error
How can I do that? Here is my code
<div class="form-group" ng-class="{ 'has-error' : submitted && form.field.$invalid }">
<div class="col-md-12"><label for="field" >Select at leat one</label></div>
<div class="col-md-2" data-ng-repeat="i in [1,2,3,4]">
<label><input type="checkbox" name="field[$index]" value="{{i}}" data-ng-model="form.field[$index]" required/>Choice {{i+1}}</label>
</div>
<div ng-show="submitted && form.field.$invalid" class="help-block">
<p ng-show="form.field.$error.required">Please select at least one choice<p>
</div>
</div>
</div>
You can use native javascript functions to iterate over your array. First that come in mind are Array.prototype.some() and Array.prototype.filter().
With some, you can determine if one item in your array validates true and with filter you can find all the elements that pass a custom expression and check if the newly created array has a certain length.
E.g.:
var atleastOne = this.form.field.some(function(element) {
return element;
});
if(atleastOne) {
window.alert("validated with some");
}
var filtered = this.form.field.filter(function(element) {
if(element) { return element; }
});
if(filtered.length > 0) {
window.alert('validated with filter');
}
You can flag the variables that show the errors based on the results of your validation. A working demo can be found here.
You can create your checkbox list object array properly then bind it. Please find the demo
<div ng-controller="ChckbxsCtrl">
<div ng-repeat="chk in items">
<div style="width:500px;height:100px;border:1px solid #000;">
<B>{{chk.group}}</B><br>
<label ng-repeat="vale in chk.values">
<input type="checkbox" ng-model="vale.val" />
{{vale.label}}</label>
</div>
<span ng-show="chk.isValid">Select at least one option from {{chk.group}}</span>
</br> </div>
</br>

angular select $touched validation

I have a form with select and a button that is enabled when the form is validated. It works fine with input box. However, it the $touched does not seem to be working properly for the select. The button is enabled even when select is touched. It is supposed to turn invalid when select is touched. It only turns invalid and the button is disabled when I select an option and then select the default value. I want it to work when select is touched and user the mouse pointer away.
Here is my html:
<form role="form" name="frameVersionEditor" novalidate class="form-horizontal col-md-12">
<div class="row">
<div class="form-group col-lg-2 col-lg-push-1" ng-class="{'has-error' : frameVersionEditor.distributor.$invalid && frameVersionEditor.distributor.$touched}">
<label>Distributor</label>
<select name="distributor" data-ng-model="myDistr" data-ng-options="distributors.key as distributors.value for distributors in distributorOptions" class="form-control" required>
<option value="">Select Distributor</option>
</select>
<span ng-show="frameVersionEditor.distributor.$error.required && frameVersionEditor.distributor.$touched" class="help-block">Please select a distributor</span>
</div>
</div>
</form>
<button data-ng-click="generate()" ng-disabled="frameVersionEditor.$invalid">Generate</button>
Here is my controller:
var myApp = angular.module('myApp',[]);
myApp.controller('myController', ['$scope', function($scope) {
$scope.myDistr = [];
$scope.distributors =
[
{
'key': '0',
'value': 'A'
},
{
'key': '1',
'value': 'B'
},
{
'key': '2',
'value': 'C'
}
];
$scope.generate = function() {
//Do something
};
}]);
I had much the same issue just a short time ago. I solved it by adding a few things to the select field and another entry to the array of possible selections.
First, you want to add an empty or invalid selection to the list of possible selections, the best place is in the [0] slot and you'll see why further down.
vm.distributors = [ "Select Distributor", "A", "B", "C" ];
Next, you need to add and update some Angular Directives for your select field. You want the ng-valid to tell Angular what is acceptable here. You also need to specify that you are starting the field with a "select something else" value and that it is not valid for that value to be selected during submit. Adding a name and id are generally best practice items. The last bit to add is the ng-required directive, since you used novalidate on the form, but it is required that this form item change to something valid. The end result is below:
<div class="form-group col-lg-2 col-lg-push-1" ng-class="{'has-error' : vm.myDistr == vm.distributors[0] && frameVersionEditor.$dirty
|| frameVersionEditor.distributor.$pristine && frameVersionEditor.distributor.$touched}">
<label>Distributor</label>
<select data-ng-model="vm.myDistr" id="myDistr" name="myDistr" ng-init="vm.distributors[0]" class="select form-control skinny" ng-required="true"
data-ng-options="d for d in vm.distributors" ng-valid="vm.myDistr != vm.distributors[0]"></select>
<p class="required" ng-show="vm.myDistr == vm.distributors[0]">*</p>
<p class="good" ng-show="vm.myDistr != vm.distributors[0]">✔</p>
<span ng-show="vm.myDistr == vm.distributors[0] && frameVersionEditor.$dirty || frameVersionEditor.distributor.$pristine
&& frameVersionEditor.distributor.$touched" class="help-block">Please select a distributor</span>
This also was updated to illustrate the simplest implementation (since you are using 0-4 as keys, you don't really need them, just use a basic array). I also changed your code to coincide with John Papa's best practices and added in a red asterisk (for invalid selections) and a green check mark (for a correct selection), as is another good practice (show success as well as failure) in the case of non-English-speaking users.
You also do not need the beginning option you specified originally since my updates handle that nicely.
By request, here is a Plunker.
I hope this helps.
-C§
I have made up this fiddle and everything seems to work fine.
<div ng-app="myApp" ng-controller="myController">
<h1>Select a Distributor</h1>
<form name="form" class="form-horizontal col-md-12" novalidate>
<div class="row">
<div class="form-group col-lg-2 col-lg-push-1" ng-class="{ 'has-error': form.distributor.$invalid && form.distributor.$touched }">
<label>Distributor</label>
<select name="distributor" ng-model="mySelection" ng-options="d.key as d.value for d in myDistr" class="form-control" required>
<option value="">Select Distributor</option>
</select>
<span ng-show="form.distributor.$error.required" class="help-inline">Please select a distributor</span>
</div>
</div>
</form>
<hr/><hr/>
<button class="btn btn-primary" data-ng-click="generate()" ng-disabled="form.$invalid || form.distributor.$touched">Generate</button>
</div>
I have the suspect that maybe the $touched event is not what you think.
Please give me some feedback if I'm wrong!

Dynamic ng-model name in AngularJS form

I have an AngularJS form where I'm using ng-repeat to build the fields of the form dynamically based on another selection. I'm trying to generate the model name dynamically and am running into problems.
<div class="form-group" ng-repeat="parameter in apiParameters">
<label for="{{parameter.paramName}}" class="col-sm-2 control-label">{{parameter.paramTitle}}</label>
<div class="col-sm-3">
<input type="text" class="form-control" name="{{parameter.paramName}}" id="{{parameter.paramName}}" ng-model="formData.parameters.{{parameter.paramName}}" placeholder="{{parameter.paramTitle}}">
</div>
</div>
What I need is it to eval to something like ng-model="formData.parameters.fooId" or ng-model="formData.parameters.barId"
The above results in an error:
Error: [$parse:syntax] Syntax Error: Token '{' is an unexpected token at column 21 of the expression [formData.parameters.{{parameter.paramName}}] starting at [{{parameter.paramName}}].
In my controller I have:
$scope.formData = {};
$scope.formData = {
settings:
{
apiEndpoint: '',
method: 'get'
},
parameters: {}
};
I've also tried ng-model="formData.parameters.[parameter.paramName]" (based on this question How can I set a dynamic model name in AngularJS?), but that doesn't eval and fails to set the ng-model value. I'm not sure if what I'm trying to accomplish is even possible. I'm assuming I need to go through the controller to achieve what I want, but any help would be appreciated.
You just need to use hash key as model:
<div class="form-group" ng-repeat="parameter in apiParameters">
<label for="{{parameter.paramName}}" class="col-sm-2 control-label">{{parameter.paramTitle}}</label>
<div class="col-sm-3">
<input type="text" class="form-control" name="{{parameter.paramName}}" id="{{parameter.paramName}}" ng-model="formData.parameters[parameter.paramName]" placeholder="{{parameter.paramTitle}}">
</div>
</div>
There is many other approaches, but this one are simpler than others.
I have tested this solution but it has a problem for my self. The problem is that the parameter "formData" is assigned to each iteration individually. In the other words if you insert a pre tag in every iteration you will see the value of each iteration individually and you cannot get all the formData in your controller after submitting the form.
For this I have found a very simple solution and it is the ng-init !!!!
Simply add this code to your form and before your repetitive tag. For the example of this question we will have:
<form ng-init="formData = []">
<div class="form-group" ng-repeat="parameter in apiParameters">
<label for="{{parameter.paramName}}" class="col-sm-2 control-label">{{parameter.paramTitle}}</label>
<div class="col-sm-3">
<input type="text" class="form-control" name="{{parameter.paramName}}" id="{{parameter.paramName}}" ng-model="formData.parameters[parameter.paramName]" placeholder="{{parameter.paramTitle}}">
</div>
</div>
</form>

Resources