Dynamic ng-model name in AngularJS form - angularjs

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>

Related

Angular js ng messages show error on init

In my project I have angularjs 1.5.10 and ng-messages 1.5.10. I use it in simple form:
<form class="form-horizontal" ng-submit="createBooking()" role="form" name="createBookingForm" novalidate>
<div class="form-group">
<label for="payment_details_last_name" class="col-sm-3 control-label">Last name</label>
<div class="col-sm-6">
<input type="text"
id="payment_details_last_name"
name="payment_details_last_name"
class="form-control"
ng-model="payment_details.last_name"
ng-required="true"
ng-maxlength="30" />
<div ng-messages="createBookingForm.payment_details_last_name.$error" role="alert">
<div ng-message="required">This field is required</div>
<div ng-message="maxlength">Your field is too long</div>
</div>
</div>
<div class="col-sm-3"></div>
</div>
</form>
In controller:
$scope.members = [{
'first_name': '',
'last_name': '',
'email': '',
'phone_number': '',
'date_of_birth': '',
}];
And on initial I always see error This field is required. I tried to create empty object in controller:
$scope.createBookingForm = {};
I also show form after initialized <div ng-if="initialized">
Nothing is working. Project using angular-seed skeleton
I saw dirty hacks like if $touched else, but I want to understand what is wrong...
Nothing is wrong about it, check out the sample in the official guide. They have required error shown by default. It seems wrong for the first glance, but this is the only correct way to handle such a case.
So basically, ng-messages does exactly what docs says:
ngMessages is a directive that is designed to show and hide messages
based on the state of a key/value object that it listens on.
the directive works in a real time and shows errors for current input state, that makes sense.
So if you want no messages to be shown by default, you should use some of approaches, discussed hundred of times
How to make ngMessage for required fields only show when dirty or submitting a form?
Angular : ng-message validation on submit click
AngularJS: Hiding ng-message until hitting the form-submit button
AngularJS 1.5.6 reset form & ng-messages
If you need some other improved behavior - there is no quick solution at the moment, you have to come up with your own validation engine that fit your needs.

AngularJS - how to sync result of calculated input field to a scope variable

I'm trying to sync the result of a calculated form field into a scope variable. For example:
<div class="form-group">
<label for="val1" class="control-label">Val 1</label>
<input class="form-control" name="val1" type="number" ng-model="data.val1" id="val1">
</div>
<div class="form-group">
<label for="val2" class="control-label">Val 2</label>
<input class="form-control" name="val2" type="number" ng-model="data.val2" id="val2">
</div>
<div class="form-group">
<label for="score" class="control-label">Score</label>
<input class="form-control" name="score" value="{{data.val1+data.val2}}" type="text" id="score">
</div>
<br/>data: {{data}}
How can I sync the result (i.e. the score field) into the scope variable $scope.data.score? I have tried ng-model="data.score" but that breaks the calculation.
You can see the example in action here:
http://plnkr.co/edit/fc9XcyyYGtAk0aGVV35t?p=preview
How do I get the last line to read data: {"val1":1,"val2":2,"score":3}?
Note that I'm looking for a solution that involves minimal to no code support at the controller level. For example, I know you can set up a watch in the controller for both val1 and val2 and then update the score in the watcher. This is what I wanted to avoid, if it's possible in angular at all. If it's not (theoretically) possible, I'd really appreciate an explanation of why it's not.
A quick background might help. Basically we have a simple form builder app that defines a form and all its fields in an xml file. Here's a sample of what the xml would look like:
<form name="test">
<field name="val1" control="textbox" datatype="number">
<label>Val 1</label>
</field>
<field name="val2" control="textbox" datatype="number">
<label>Val 2</label>
</field>
<field name="score" control="textbox" datatype="number">
<label>Score</label>
<calculate>[val1]+[val2]</calculate>
</field>
</form>
When a form is requested, the system will need to pick up the xml, loop through all the fields and generate an angular style html to be served to the browser and processed by angular. Ideally, I want to keep all the form specific logic (validation, show/hide, calculation etc) confined to the html, and keep the controller (js) logic generic for all forms.
The only solution I can come up with is to dynamically load watcher functions, through something like this: eval("$scope.$watch('[data.val1,data.val2]')..."), but as I said, I really want to avoid this, because it's just tedious, and feels extremely dodgy :)
The first dirty way.
In your case you can move all logic from controller into html with using combination of ng-init and ng-change directives.
<div class="form-group">
<label for="val1" class="control-label">Val 1</label>
<input class="form-control" name="val1" type="number" ng-model="data.val1" ng-change="data.score = data.val1 + data.val2" id="val1">
</div>
<div class="form-group">
<label for="val2" class="control-label">Val 2</label>
<input class="form-control" name="val2" type="number" ng-model="data.val2" ng-change="data.score = data.val1 + data.val2" id="val2">
</div>
<div class="form-group" ng-init="data.score = data.val1 + data.val2">
<label for="score" class="control-label">Score</label>
<input class="form-control" name="score" ng-model="data.score" type="text" id="score">
</div>
<br/>data: {{data}}
I don't think that it's the clearest solution, but you can leave your controller without any changes with it.
Demo on plunker.
The second dirty way.
There is one more way, but now you don't need ng-init and ng-change directives. You can add just one dirty expression in html:
<div id="main-container" class="container" style="width:100%" ng-controller="MainController">
{{data.score = data.val1 + data.val2;""}} <!-- key point -->
<div class="form-group">
<label for="val1" class="control-label">Val 1</label>
<input class="form-control" name="val1" type="number" ng-model="data.val1" id="val1">
</div>
<div class="form-group">
<label for="val2" class="control-label">Val 2</label>
<input class="form-control" name="val2" type="number" ng-model="data.val2" id="val2">
</div>
<div class="form-group">
<label for="score" class="control-label">Score</label>
<input class="form-control" name="score" ng-model="data.score" type="text" id="score">
</div>
<br/>data: {{data}}
;"" in expression stops evaluating of angular expression to text in html.
Demo on plunker.
See if this works, in your HTML change,
<input class="form-control" name="score" ng-model = "data.score" type="text" id="score">
and, in your controller do,
var myApp = angular.module('myapp', [])
.controller('MainController', function($scope) {
$scope.data = { val1: 1, val2: 2, score: 3};
$scope.$watch('[data.val1,data.val2]', function (newValue, oldValue) {
$scope.data.score = newValue[0] + newValue[1];
}, true);
})
Demo plunk, http://plnkr.co/edit/gS0UenjydgId4H5HwSjL?p=preview
If you want to know how you can do it, then i have one solution for you make a ng-change event for both of your text box and sum both the number there and use ng-model in the third text box then, you can see it will work as per your need.
For the first time load you need to calculate it out side only.

Angular form name is passed as string when passed as parameter

I'm simply trying to reset a form using the angular functions $setPristine & $setUntouched (several forms are created with ng-repeat).
I assign the form name dynamically by using the syntax {{ someName }} (the name is build on the server side and is passed as json (string)).
The name of the form is correctly assigned in the markup and validations are working as expected. The problem arrises when I pass that name as a parameter in the ng-click="reset(someName)" function.
When debugging the name comes as a string and not as the form object which causes the error. I did a quick test by hard-coding the name and pass that same name and it works fine.
My assumption is, the name coming from json is a string and the type is forwarded to the function as is, instead of the object.
So the question is: is there a way to convert that name so it is interpretated correctly by the controller. Or maybe there is something else I'm missing...
Here is the markup ( notice the name of the form uses {{ resto.contactForm }} ):
<form novalidate name="{{ resto.contactForm }}" ng-submit="submit(restoContact, resto.contactForm.$valid)" class="sky-form">
<div class="form-group">
<label class="checkbox state-success">
<input type="checkbox" ng-model="restoContact.sameAsUser" name="sameAsUser" id="sameAsUser" value="true" ng-click="contactAutoFill()"><i></i>Contact name is same as current user.
<input type="hidden" name="sameAsUser" value="false" />
</label>
</div>
<div class="form-group">
<label class="control-label" for="contactName">Contact Name</label>
<input type="text" ng-model="restoContact.contactName" name="contactName" id="contactName" placeholder="John, Doe" class="form-control" required />
<div ng-show="{{ resto.contactForm }}.contactName.$error.required && !{{ resto.contactForm }}.contactName.$pristine" class="note note-error">Please enter a name or check the box 'Same as current user'.</div>
</div>
<div class="form-group">
<label class="control-label" for="contactPhoneNumber">Contact Phone Number</label>
<input type="text" ng-model="restoContact.contactPhoneNumber" name="contactPhoneNumber" id="contactPhoneNumber" placeholder="+1 555-1234-567" class="form-control" required ng-pattern="phoneNumberPattern" />
<div ng-show="({{ resto.contactForm }}.contactPhoneNumber.$error.required || {{ resto.contactForm }}.contactPhoneNumber.$error.pattern) && !{{ resto.contactForm }}.contactPhoneNumber.$pristine" class="note note-error">Please enter a valid phone number.</div>
</div>
<div class="margin-leftM19">
<button class="btn btn-primary">Save Changes </button>
<button class="btn btn-default" ng-click="reset(resto.contactForm)">Cancel </button>
</div>
</form>
Here is the reset function in the controller (form comes as "contactForm1" which is the correct name but is a string and not the object):
$scope.reset = function (form) {
if (form) {
form.$setPristine();
form.$setUntouched();
}
//$scope.user = angular.copy($scope.master);
};
I have not implemented th submit method but I'm sure I will be running into the same issue.
Any suggestions or advices are welcome.
Thanks in advance...
Here is the fidle.js. the variable data is an exact response from the server.
[http://jsfiddle.net/bouchepat/v0mtbxep/]
SOLUTION:
http://jsfiddle.net/bouchepat/v0mtbxep/3/
I removed $setUntouched as it throws an error.
You can't dynamically name a <form> or <ng-form>.
Although what you want, is make the form usable in the controller. You could do the following:
// in controller
$scope.form = {};
$scope.reset = function() {
$scope.form.contact.$setPristine();
$scope.form.contact.$setUntouched();
};
// in html
<form name="form.contact">
This is happening because resto.contactForm is a string defined on the scope. The angular directive for form is just creating a variable on the scope with the same name. To get the variable by a string, use $eval. This should work:
$scope.reset = function (formName) {
var form = $scope.$eval(formName);
if (form) {
form.$setPristine();
form.$setUntouched();
}
//$scope.user = angular.copy($scope.master);
};

AngularJS - dynamic naming using $index in expression

I have a directive in which i create many ng-form's and i name each form based on the $index (from the ng-repeat). My problem is that i want to show an error container (that contains the error message) when the form is invalid but i cant find how to properly reference the form.
Here is my code:
<ng-form name="innerForm{{$index}}">
<label ... ><input name="input" ... />
<div class="error-container" ng-show="'innerForm'+$index.input.$invalid">
// show the error message
</div>
</ng-form>
I want 'innerForm'+$index.input.$invalid to be evaluated as innerForm5.input.$invalid for example.
I have made many attempts but i can't get it to work. What is the correct way to reference my dynamically named form?
please see here : http://jsbin.com/jocane/4/edit
<div ng-controller="firstCtrl">
<div ng-repeat="object in allMyObjects">
<ng-form name="innerForm{{$index}}">
<input type="text" ng-model="object.name" required name="userName">
<div ng-show="{{'innerForm'+$index}}.userName.$invalid">
error
</div>
</form>
</div>
First thing is that, ng-form creates an isolated scope. So if you give a ng-form static name, it will be ok. No need to append $index or anything.
ng-show="innerForm.input.$invalid"
Code snippet to find form controller using element
var elmParent = $(domEle).parents('form, ng-form, [ng-form]');
var formCtrl = null;
var elemCtrl = null;
if (elmParent.length) {
var elm = angular.element(domEle);
formCtrl = elm.scope().$parent[elmParent.attr('name') || elmParent.attr('ng-form')];
}
Hope this will help

ng-class within ng-repeat having dynamic ng-model

I am iterating over a array of strings with ng-repeat
<div ng-repeat="i in mdsecuritysensorswsize">
in there I have a required select with a 'dynamic model'
ng-model="valueBag[i+'sizes']"
now I want to add a has-error class, if the select is empty (apply bootsrap3 error style)
ng-class="{'has-error': myForm.valueBag[i+'sizes'].$invalid, 'has-success': !myForm.valueBag[i+'sizes'].$invalid}"
But this doesn't seem to work with such a dynamic ng-model name.
Here is the complete code:
<div ng-repeat="i in mdsecuritysensorswsize">
<div class="form-group">
<div class="col-md-2"
ng-class="{'has-error': myForm.valueBag[i+'sizes'].$invalid, 'has-success': !myForm.valueBag[i+'sizes'].$invalid}"
>
<select class="form-control" required
ng-options="resText[j] for j in mdsecuritysensorswsizesizes2"
ng-model="valueBag[i+'sizes']"></select>
</div>
</div>
</div>
Am I missing something here?
this from the comments did the job:
if you create your form inputs dynamically, it will be a problem with this code when you try to access myForm.valueBag[i+'sizes'].$invalid If you create form inputs dynamically, check out my directive to solve this problem here: stackoverflow.com/questions/21455695/… – Khanh TO Feb 3 at 10:04
AngularJS dynamic form field validation
THX #Khanh TO

Resources