AngularJS Data Binding Breaks ngRepeat + Strange Behavior - javascript

Background: I'm a systems guy who switched to Front End development. Just dorking around with Angular. Got creative with data binding and now I'm trying to understand this behavior:
<div ng-app>
<div class="container">Name
<input type="text" ng-model="user.name">
</div>{{ user.name }}
<div class="container">Name
<input type="text" ng-model="user.name">
</div>
<ul ng-model="user.name">
<li ng-repeat="l in user.name">{{ l }}</li>
</ul>
</div>
http://jsfiddle.net/Lpm74dd8/
I would expect this to take my input from either box, mirror the text between the input boxes, and repeat each letter on its own line at the bottom.
If I type "test" in one of the inputs, ng-repeat will break when a letter repeats.
Typing the alphabet sequentially works as I expect.
Why does ng-repeat break when input letters are duplicated? I have no practical use for this, I was just experimenting with Angular for fun and came across this and don't understand it.

ng-repeat will not accept duplicate entries. As test has t as duplicate entry, it will fail.
Add track by expression to avoid this.
When we do not provide any track by expression the entry(in this case each letter) itself is treated as a unique identifier to link the entry to the list to track the changes in it.
<li ng-repeat="l in user.name track by $index">{{ l }}</li>

Usetrack by $index with ng-repeat for dupilcate entries in array. Try this fiddle http://jsfiddle.net/1Lw11bqr/

Related

Nested ng-repeat with dynamic input

I am trying to display a JSON response in a table using ng-repeat. The problem is that not all objects recieved are the same. All of them have a date, short message and long message. There are also ones with an additional value list, differing in length. This list should be diplayed underneath the long message within its own table or list. I use the alert.slice().reverse() because I want the newest entries to be on top. The new objects are inserted using .push({values}).
<tbody class="AlTbody" ng-repeat="alerts in alerts.slice().reverse()" ng-class="className">
<tr class="Altr Aldate">
<td ng-show="{{alerts.Date}}"><b>{{alerts.Date}}:</b>
</td>
</tr>
<tr class="Altr Alshort " ng-click="toggleDetail($index)">
<td>{{alerts.S}}</td>
</tr>
<tr class="Altr " ng-show="activePosition == $index">
<td class="msgL">{{alerts.L}}
<!-- 1) <p ng-show="{{item.List}}"><br><ul><li>Previous values:</li> <li ng-repeat="vals in ValueList">{{vals.value}}</li></ul> </p>-->
<!-- 2) <p ng-show="{{List.txt}}"> <br><ul><li>Previous values:</li> <li ng-repeat="List in alerts.List">{{List.txt}}</li></ul> </p>-->
</td>
</tr>
</tbody>
I already tried two approaches as seen in the code. The first one displayed the list correct however it was displayed underneath every long message instead of only the one it belongs to. I used a new variable.
var l=valList.length;
scope.List=true;
while(l>-1){
scope.ValueList.push({value: valList[l]});
l--;
}
The second approach did not work at all because I could not find an index.
var l=valList.length;
var indexV= jQuery.inArray(currdate,scope.alerts.Date);
while(l>-1){
scope.alerts[indexV].List.push({txt: valList[l]});
l--;
}
edit:
This is the current output. There you can see two objects( date, short message and long message) and both of them have the previous values section. However only the upper object is supposed to diplay the list of previous values.
What you are trying to achieve is entirely possible, my advice is to start at a known point and work from there.
I have put together a jsfiddle to show you how nested ng-repeats will work. try and work from that point. As a side note it looks like your JSON structure is overly complex, if you can simplify that down I would.
https://jsfiddle.net/roscorcoran/wyu7tgxm/
<div ng-app="App" ng-controller="Ctrl1">
<div ng-repeat="alert in alerts">
<a ng-if="alert.Date">{{alert.Date}}</a>
<p ng-repeat="val in alert.L.vals">
<a ng-if="val && val.value">{{val.value}}</a>
</p>
<p ng-repeat="item in alert.List">
<a ng-if="item && item.txt">{{item.txt}}</a>
</p>
</div>
</div>
You can try to add a ng-if statement inside the ng-repeat loop:
<p ng-if="{{values.list}}"><br><ul><li>Previous values:</li> <li ng-repeat="vals in values.list">{{vals.value}}</li></ul> </p>-->
Okay so this works for me now. It still always displays "Previous Values:" but the values only display when they actually belong to the message.
<ul ng-if="alerts.List.vals"><li>Previous values:</li> <li ng-repeat="val in alerts.List.vals" >{{val.value}}</li></ul>
This might not be the best and most elegant solution but it works.
if(valList){
scope.alerts.push({Date:currdate,S:msgSn,L:msgLn, List:{ vals:[{value:valList[0]},{value:valList[2]},{value:valList[4]},{value:valList[6]}] } });
}else{
scope.alerts.push({Date:currdate,S:msgSn,L:msgLn });
}
I only display the even indexes of the value array because the list of values was a string which I split and every uneven entry is "):" which I don't need to display.

How to show the name of the variable in an AngularJS website?

I'm trying to show the name of the variable in my website written with AngularJS.
for example:
Backend code:
$scope.Bundles = {
Bundle1:["Sensor1","Sensor2"],
Bundle2:["Sensor1","Sensor2","Sensor3"],
Bundle3:["Sensor1","Sensor2","Sensor3","Sensor4"]
}
Frontend code:
<label ng-repeat="name in Bundles">
<div> *Want to show "Bundle#" (name), instead of it's value (the sensors)* </div>
</label>
Some notes:
From google searches, I always find people asking how to show the value of the variable, {{name}}, I know that, haven't found anything like the question im asking here
I need it because later in the code I will need to do a ng-repeat with "sensor in name" to show the sensors separately, so I really need a concrete solution and not a dirty one with another array holding the names.
Thank you.
You can access both the key and value using:
<label ng-repeat="(key, value) in Bundles">
<div> *Want to show {{ key }}, instead of it's value {{ value }} (the sensors)* </div>
</label>
You can use the tuple returned by the ng-repeat as stated in the docs https://docs.angularjs.org/api/ng/directive/ngRepeat
<label ng-repeat="(key,value) in Bundles">
You can use javascipt Object.keys(arr) to get all the keys and then iterate to the keys using ng-repeat.
$scope.BundleNumbers = Object.keys($scope.Bundles)
$scope.BundleNumbers will have ["Bundle1", "Bundle2", "Bundle3"]
<label ng-repeat="name in BundleNumbers">
{{name}}
</label>
The other and efficient/angular way to do is :
<label ng-repeat="(key,value) in Bundles">{{key}} </label>
Reference links:
Angular JS ng Repeat
JavaScript Object keys

ng-model binding not updates another ng-model on the same object

I have this code
http://plnkr.co/edit/aycnNVoD96UMbsC7rFmg?p=preview
<div data-ng-app="" data-ng-init="names=['One']">
<input type="text" ng-model="names[0]">
<p>Looping with ng-repeat:</p>
<ul>
<li data-ng-repeat="name in names">
<input type="text" ng-model="name"> {{ name }}
</li>
</ul>
</div>
When i change value of name[0] in the first input box it changes values of the second input box.
But when i change value of name[0] in the second input box it does not change value of the first input box. Why?
It works if you bind your second input to : names[$index]
<input type="text" ng-model="names[$index]"> {{ name }}
This is due to ng-repeat creating a child scope, so the reference to name inside the ng-repeat is different to that original one in the names array, see here:
New AngularJS developers often do not realize that ng-repeat,
ng-switch, ng-view and ng-include all create new child scopes, so the
problem often shows up when these directives are involved. (See this
example for a quick illustration of the problem.)
Regarding as to why this happens, when you bind the input to name in names inside the ng-repeat, you are creating a new property on the new child scope created by the ng-repeat called name, and thus the ng-model of the textbox created by the ng-repeat is referencing a different name to that of the actual 0th element of the names array. As others have pointed out, if you use names[$index] you are implicitly referencing the 0th element of the names array, thus NOT creating a new name property on the child scope created by the ng-repeat. An angular best practice is not to have ng-models bound to primitives, rather objects, Sandy has mentioned in his answer if you bind to an object you will overcome this, and the 2 other posters have answered this by using $index to refer to the 0th element of the names array. This is one of the nucances of scope inheritance in angular.
A couple more handy links:
Here and here.
Just wanted to give my bit on this. Somewhat related to your problem as I see.
<body>
<div data-ng-app="" data-ng-init="names=[{value:'One'}, {value:'Two'}]">
<p>Looping with ng-repeat:</p>
<ul>
<li data-ng-repeat="name in names">
<input type="text" ng-model="name.value"> {{ name }}
</li>
</ul>
</div>
</body>
Instead of binding the array item directly to the control, I would prefer to create an object of the array and then bind value of each item. This way we can avoid reference problems.
A working prototype jsfiddle
Hope it helps.
You need to provide $index in your ng-model.
<li data-ng-repeat="name in names">
<input type="text" ng-model="names[$index]"> {{ name }}
</li>
You are binding ng-model="names[0]". So it means that you are binding value on first index of names array.
So when we write ng-model="names[$index]" in ng-repeat it means that all values will be bound accordingly into array. $index is an iterator offset of the repeated element.
names[0] = 'One'
names[1] = 'Two'
and so on!

AngularJS - how to show count 0 when ng-repeat and filter returns no rows

I am trying to print the count of elements filtered through a condition in AngularJS.
I am using ng-repeat to loop over collection and filter it to get counts and it works fine when there is value in it. However, when the filter returns empty result, Angular doesn't render the element and hence, I can't get it to display 0 count.
http://plnkr.co/edit/KNVwf2Yckxf1Qcyzcmea?p=preview
<div ng-repeat="i in iArr|filter:i.name='tim'">{{i.vals.length}}</div>
Does anyone have a clue to make it work in simple fashion.
Edit:
The question How to show a message when filter returns nothing in ng-repeat - AngularJS has a reply which actually solved my question but I don't know how. I am wondering if there is a very simple way to do it.
From that answer:
<select ng-model="shade" ng-options="shade for shade in shades"></select><br>
<ul>
<li ng-repeat="c in filteredColors = (colors | filter:shade)">{{c.name}}</li>
</ul>
<div ng-show="!filteredColors.length">No colors available</div>
The key is in c in filteredColors = (colors | filter:shade). The array result of the filter expression colors | filter:shade is being set to filteredColors, which then becomes available on the $scope object. Because of this, it can be used elsewhere in that controller scope. This is why it can be checked for its length to see if there are no colors.
Here is a working plnkr with solution and some extra
http://plnkr.co/edit/eOmHhR1VWjfYzHYqiEY1?p=preview
<body ng-controller="MainCtrl">
<label><input type="text" ng-model="search.vals">vals</label>
<label><input type="text" ng-model="search.name">name</label>
<div ng-repeat="i in filteredArr = (iArr | filter:search)">{{i.vals}} | {{i.name}}</div>
<div ng-if="!filteredArr.length">0</div>
</body>

Select different child JSON element with select input in AngularJS

I am printing a list of food types by making an API call per category. The food in the categories have this JSON structure:
{"uid":"56",
"title":"Nussbrot",
"code":"X 39 2000000",
"final_factor":"0",
"sorting":"0",
"unit":[
{"title":"Scheiben",
"gram":"45",
"max":"100"
},
{"title":"Messerspitzen",
"gram":"250",
"max":"12"}
]
}
I am looping through & printing the values out into a template. No problem. I am printing the "unit" values into a select box:
<option ng-repeat="title in food.unit">{{ title.title }}</option>
And I am currently printing out the grams & title of the first unit in each food like this:
<div class="max">Max is: {{ food.unit[0].max }}</div>
<div class="grams">Grams is: {{ food.unit[0].gram }} </div>
How can I make this dynamic, so that I am printing out the max & grams of the currently selected unit?
Here's my Plunkr.
Angular makes dealing with options and selected options very easy. You should stop thinking in terms of indexes or value. With angular you can bind the entire object, so there's no need to look it up. For example you could do the following for your select:
<select ng-model='selectedUnit' ng-options="unit as unit.title for unit in food.unit"></select>
Let me briefly explain the expression for ng-options
unit in food.unit means we will iterate over the food.unit array storing each value in unit as we go along.
unit as unit.title means what we are putting in the ng-model whenever the user selects an item is the entire unit object itself. The as unit.title tells angular to use the title of the unit as a display for the option.
What this ends up doing is that whenever the user selects one of the options, the entire unit object will be stored in the ng-model variable (in this case selectedUnit). This makes it really easy to bind it elsewhere. For example you can just do:
<div class="unit">Unit is: {{ selectedUnit.title }}</div>
<div class="max">Max is: {{ selectedUnit.max }}</div>
<div class="grams">Grams is: {{ selectedUnit.gram }} </div>
In angular, if you find yourself dealing with indexes or ids and then looking things up by id or index then you are typically doing it wrong. One of the biggest advantages of using angular is how easy it is to deal with objects, and you should really take advantage of it.
For example, I often see newbies doing something like
<li ng-repeat="person in persons">{{person.name} <a ng-click="savePerson(person.id)">Save</a></li>
And then in their code they use the id to look up the person from an array:
$scope.savePerson = function(id){
var person = persons[id];
$http.post('/persons/'+id, person);
};
This kind of lookup is almost always unecessary with angular. You can almost alway just pass the person right away:
<li ng-repeat="person in persons">{{person.name} <a ng-click="savePerson(person)">Save</a></li>
And then have the click handler take the person:
$scope.savePerson = function(person){
$http.post('/persons/'+person.id, person);
};
I know I strayed a bit from your original question. But hopefully this makes sense and helps you write things more simply using the "angular way"
Her is the plunkr for your example:
http://plnkr.co/edit/lEaLPBZNn0ombUe3GPa9?p=preview
you can fist of all handle the selected item with the ng-selected:
http://docs.angularjs.org/api/ng.directive:ngSelected
<select>
<option ng-repeat="title in food.unit" ng-selected="selectedIndex=$index">{{ title.title }}</option>
</select>
<div class="max">Max is: {{ food.unit[selectedIndex].max }}</div>
<div class="grams">Grams is: {{ food.unit[selectedIndex].gram }} </div>
This should propably work ;) Havn't tryed it yet!

Categories