Elegant solution for a dynamic ng-repeat form - angularjs

I am new to AngularJS and I have following problem:
I want to iterate over an array of 'attributes' with a bunch of keys for the values stored in the Object.
<div ng-repeat="key in attributes">
{{key}}: <input type="text" value={{Object.key}} name="{{key}}">
</div>
This code displays just the correct key of {{column}} but delivers no result for the value of {{Object.column}}.
The phrase {{Object.{{column}}}} dosn't work neither.
If I run the code, giving the Object a static key of (e.g. ID) everything works perfectly.
I could go for
<div>
id: <input type="text" value={{Object.id}} name="id">
name: <input type="text" value={{Object.name}} name="name">
value: <input type="text" value={{Object.value}} name="value">
and so on...
</div>
But this static form does not seem to be the perfect solution.
Can someone help me?

You should do something like this -
<div ng-repeat="key in attributes">
{{key}}: <input type="text" value={{ Object[key] }} name="{{ Object[key] }}">
</div>

Use {{Object[key]}}. Angular considers .key to be a constant not a variable.

Related

Using protractor to test an angularjs app, how do i keep a reference to one of the rows of element.all when the array changes as I work with it?

I have an ng-repeat which has a new element added to it as soon as the existing "last" element is modified in any way. My protractor test looks something like this:
var emptyPerson = this.people.last() // this gets me the last row of the ng-repeat
emptyPerson.element(by.css('.firstName')).sendKeys('first'); // this sets the firstname correctly but then the app adds a new row to the ng-repeat because of the model change here.
emptyPerson.element(by.css('.lastName')).sendKeys('last'); // this sets the lastname of the new row instead of the row that emptyPerson previously referenced
Is there any way to essentially tell emptyPerson to stick to the same element until we're done with it?
Example HTML before firstname is edited:
<div ng-repeat="person in object.People" class="student ng-scope">
<div type="text" ng-model="person.FirstName" skip-label="true" placeholder="First" validator="svalidators[$index]" class="layout-default field field-FirstName type-text">
<input type="text" ng-focus="myFocus()" ng-blur="myBlur()" class="form-control" placeholder="First" ng-model="lModel.val" name="person-FirstName">
</div>
<div type="text" ng-model="person.LastName" skip-label="true" placeholder="Last" validator="svalidators[$index]" class="layout-default field field-LastName type-text">
<input type="text" ng-focus="myFocus()" ng-blur="myBlur()" class="form-control" placeholder="Last" ng-model="lModel.val" name="person-LastName">
</div>
example after firstname is edited:
<div ng-repeat="person in object.People" class="student ng-scope">
<div type="text" ng-model="person.FirstName" skip-label="true" placeholder="First" validator="svalidators[$index]" class="layout-default field field-FirstName type-text">
<input type="text" ng-focus="myFocus()" ng-blur="myBlur()" class="form-control" placeholder="First" ng-model="lModel.val" name="person-FirstName">
</div>
<div type="text" ng-model="person.LastName" skip-label="true" placeholder="Last" validator="svalidators[$index]" class="layout-default field field-LastName type-text">
<input type="text" ng-focus="myFocus()" ng-blur="myBlur()" class="form-control" placeholder="Last" ng-model="lModel.val" name="person-LastName">
</div>
<div ng-repeat="person in object.People" class="student ng-scope">
<div type="text" ng-model="person.FirstName" skip-label="true" placeholder="First" validator="svalidators[$index]" class="layout-default field field-FirstName type-text">
<input type="text" ng-focus="myFocus()" ng-blur="myBlur()" class="form-control" placeholder="First" ng-model="lModel.val" name="person-FirstName">
</div>
<div type="text" ng-model="person.LastName" skip-label="true" placeholder="Last" validator="svalidators[$index]" class="layout-default field field-LastName type-text">
<input type="text" ng-focus="myFocus()" ng-blur="myBlur()" class="form-control" placeholder="Last" ng-model="lModel.val" name="person-LastName">
</div>
This is how Protractor works internally. It searches for the element at the time an action is applied on the element. I am afraid you have to handle it "manually" and re-reference the desired repeater item.
To make things a little bit better in terms of coding, I would hide the first interaction with the repeater item part in a function - basically, touching the input for repeater to have one more item, then returning the item before last. Something along the lines:
var MyPageObject = function () {
this.people = element(by.repeater("person in object.People"));
this.touch = function () {
var emptyPerson = this.people.last();
emptyPerson.element(by.css('.firstName')).sendKeys('smth');
var newEmptyPerson = this.people.get(-2); // I think -1 would be the last
emptyPerson.element(by.css('.firstName')).clear();
return newEmptyPerson;
}
this.fillForm = function () {
var emptyPerson = this.touch();
emptyPerson.element(by.css('.firstName')).sendKeys('first');
emptyPerson.element(by.css('.lastName')).sendKeys('last');
}
}
Try saving to a variable before it changes:
var oldLastName = emptyPerson.element(by.css('.lastName'));
emptyPerson.element(by.css('.firstName')).sendKeys('first');
oldLastName.sendKeys('last');
Hope it helps

How to apply ng-class using String syntax and array syntax?

I am a beginner in angularjs and I read in the doc of angularjs that ng-class can be applied as string and array syntax. so I was trying to apply the ng-class for the below code
(1) Using String Syntax
<p ng-class="style">Using String Syntax</p>
<input type="text" ng-model="style" placeholder="Type here">
(2) Using array Syntax
<p ng-class="[style1, style2]">Using Array Syntax</p>
<input ng-model="style1"
placeholder="Type here"><br>
<input ng-model="style2"
placeholder="Type here"><br>
I was trying to apply css like
.style{
color:red;
}
.style1{
color:blue;
}
.style2{
font-size:30px;
}
but failed. I do not know where I am doing mistake.Any help is appreciated.
you have added the ng-model="style" in input. So what ever you type in the input that will become the class for the 'p' tag because ng-class="style".
check this fiddle http://jsfiddle.net/devjit/wdtk370z/4/
And Type 'style', 'style1', 'style2' in the input field. you will understand what is happening.
<div ng-app>
<p ng-class="style">Using String Syntax</p>
<input type="text" ng-model="style" placeholder="Type here"/>
<p ng-class="[style1, style2]">Using Array Syntax</p>
<input ng-model="style1"
placeholder="Type here"/><br/>
<input ng-model="style2"
placeholder="Type here"/><br/>
</div>

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.

AngularJS - get label text for field

Question
I was wondering what the AngularJS "best practice" way of getting a label for a field is. With jQuery you just query using a "label for" query then extract the text. While it's possible to do it this way with AngularJS, something just doesn't feel right about it.
Assume you have something like this in your HTML:
<form name="MyForm" ng-controller="Ctrl">
<label for="MyField">My spoon is too big:</label>
<input type="text" size="40" id="MyField" ng-model="myField" />
<br /><br />
You entered {{ myField }} for {{ myField.label }}
</form>
The controller internal is pretty simple:
$scope.myField = 'I am a banana.';
Basically I want to populate the myField.label in the output with "My spoon is too big."
What I'm Doing Now
All I am doing right now is executing a query that pulls the data similar to the jQuery methodology ($("label[for='MyField']")). Then, if that doesn't exist I am just pulling the placeholder text. It works, but it seems like a bit of overhead.
What I'm Trying to Accomplish
I want some custom form validation and I want to include the label in the message. I just need to pull the label text so that I can write it extremely generically and then not have to worry about people switching i18n data in dynamically later in the game.
Fiddle
Per the suggested solution:
https://jsfiddle.net/rcy63v7t/7/
You change your HTML to the following:
<label for="MyField">{{ myFieldLabel }}</label>
<input type="text" size="40" id="MyField" ng-model="myField" />
and your JS to the following:
$scope.myFieldLabel = 'My spoon is too big:';
then later, you can get/set its value just as easily:
if ($scope.myFieldLabel === 'My spoon is too big:') {
$scope.myFieldLabel = 'New label:';
}
Note that new AngularJS best practices call for always using a "dot" in a field reference. It would fit perfectly here if you did something like:
<label for="MyField">{{ myField.label }}</label>
<input type="text" size="40" id="MyField" ng-model="myField.value" />
and JS:
$scope.myField = {
value: '',
label: 'My spoon is too big:'
};
Then you can always easily access $scope.myField.label and $scope.myField.value.
Let's say in your controller you have a scope variable like
$scope.myField = {};
$scope.myField.label = "Fruit name";
and your template is like
<form name="MyForm" ng-controller="Ctrl">
<label for="MyField">{{myField.label}}</label>
<input type="text" size="40" id="MyField" ng-model="myField.value" />
<br /><br />
You entered {{ myField.label }} for {{ myField.label }}
</form>
By this field label will come dynamically. Apply custom validation in input field as per your requirement.
Hope I understand exactly what you wants.
Just put your label text in the input title and you can use a "#" directive. You can also use this to make sure the label id matches.
<label for="{{myfield_control.id}}">{{myfield_control.title}}</label>
<input type="text" size="40" id="MyField" ng-model="myField" title="My spoon is too big:" #myfield_control >
<br /><br />
You entered {{ myField }} for {{ myfield_control.title }}
myField is your ngModel. myfield_control is a reference to your input control.

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