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

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!

Related

ng-model within ng-repeat not updating reference object

In my controller, I have an object:
$scope.selectedMap = {
map: '', mapDesc: '', stops_json: "", stopLength: []
};
The stops_json key in this object holds another object, that looks like this:
{
desc: "PRG-BUD-HR-PAY",
stop1:"DEPT",
stop2:"ACCT",
stop3:"ACCT2",
stop4: 158,
stop5:"CCT",
stop6:"CCA",
stop7: 156
}
I'm using this child object in an ng-repeat, and generating inputs with it for each stop:
<p class="row col-md-12">
<strong>Description: </strong>{{ selectedMap.stops_json.desc }}
</p>
<div class="row col-md-12 float-left">
<md-input-container ng-repeat="stop in selectedMap.stops_json" ng-if="!$first" ng-model="stop" class="col-md-12">
<label>Stop {{ $index }}:</label>
<input class="col-md-8 float-left mapStops" ng-model="stop" aria-label="Route Stop Description">
</md-input-container>
</div>
My issue is, that when I change the value of the input tags, the stops_json object does not change. I was under the impression that it was supposed to, since that is the object that is modeled. I am clearly mistaken, can anyone educate me?
A few problems to mention first:
The notation for iterating over objects with ng-repeat is (key, value) in obj
If you choose to use object iteration with ng-repeat then your ng-if dependent on $first is non-deterministic (i.e. iterating in key definition order is not guaranteed)
Applying an ng-model binding on the ng-repeat with the intention of making its constituents editable is incorrect—the models of each item should be have ng-model applied instead
I've created a stripped down example of your snippet to demonstrate making the repeated inputs able to change the stops_json model.
See here.
Basically, you need to ensure the input elements inside the ng-repeat directly bind to your stops_json model in order for any edits to affect it.
Also, note that all any numeric properties should use the type="number" variant of input, otherwise any edits will set the values to strings instead.

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

Maintaining an array of ints representing option indices in Angularjs

I have an array of ints in my model which is meant to represent the indices of a list of options the user will pick from using a select list. The idea is there's a list you can expand and you keep picking things from the select list. I have code along these lines:
<ul>
<li ng-repeat="item in model.arrayOfInts">
<select ng-model="item"
ng-options="choice.id as choice.name for choice in common.options">
<option value="">---</option>
</select>
<button ng-click="model.arrayOfInts.splice($index, 1)" type="button">
Delete
</button>
</li>
</ul>
<button ng-click="model.arrayOfInts.push(null)" type="button">
Add
</button>
I have two problems which seem to be related:
the ng-model does not seem to actually bind properly; if I inspect the scope I can see that newly-pushed members of arrayOfInt are still set to null even after I select something from the select menu representing them.
When I attempt to push another item into the array, Angular complains that duplicate options are not allowed.
What is the proper way to do what I'm trying to do?
JSFiddle sample
The first issue ("ng-model does not bind properly") is because you're not targeting the array with ng-model. item in your ng-repeat is a new property on the scope (created by the ng-repeat). It doesn't contain any information on where it came from (arrayOfInts).
The following code tells ng-model to point to the right index of the array, rather than a new scope property.
<select ng-model="model.arrayOfInts[$index]"
ng-options="choice.id as choice.name for choice in common.options">
The second issue is because model.arrayOfInts can have multiple duplicates in it (click "add" twice and you've just added two nulls). Angular needs some way to tell the difference between them, so you need to add a tracking property, in this case $index.
<li ng-repeat="item in model.arrayOfInts track by $index">

AngularJS: How do I bind data in nested ng-repeats?

I've got a pretty deeply nested JSON object being returned from an $http call, using Angular. In my template, I have to nest a few ng-repeats to get present the data. I can't seem to figure out how to bind the data using ng-model on a text input.
I read this question which said that the return object isn't automatically data in the $scope, and you have to loop through the data and instantiate the structure. I tried that as well with the same outcome.
// Seemingly unnecessary code
angular.forEach(Object.keys($scope.sources), function(sourcename){
$scope.sourceData[sourcename] = {};
angular.forEach(Object.keys($scope.sources[sourcename]), function(key){
$scope.sourceData[sourcename][key] = $scope.sources[sourcename][key];
});
Here's a fiddle showing my attempts:
http://jsfiddle.net/c7z9C/2/
I just want the values to be populated in the fields and bound to the model. Thanks in advance for any advice.
The HTML in your example was just a little off.
Here is a working fiddle.
The "not working" input just has some code in the ng-model that wasn't working.
First off, you don't need to interpolate {{ }} inside Angular directive attributes. So, this includes ng-model. So the {{key}} isn't necessary.
Also, sourceData was misspelled. It was supposed to be sourcedata and case matters.
So the end result for the ng-model is ng-model="sourcedata[key]":
<li ng-repeat="(key,value) in sourcedata">
WORKS: <input type="text" value="{{value}}" /><br/>
DOESN'T: <input type="text" ng-model="sourcedata[key]" />
</li>

Angular NgModelController behaviour inside ng-repeat

Once I realized how ng-model directive works and was absolutely confident about it's behaviour this example just blowed my mind
<html ng-app>
<head>
<link href="//netdna.bootstrapcdn.com/twitter-bootstrap/2.2.1/css/bootstrap-combined.min.css" rel="stylesheet">
</head>
<body ng-init="names = ['Sam', 'Harry', 'Sally']">
<h1>Fun with Fields and ngModel</h1>
<p>names: {{names}}</p>
<h3>Binding to each element directly:</h3>
<div ng-repeat="name in names">
Value: {{name}}
<input ng-model="name">
</div>
<p class="muted">The binding does not appear to be working: the value in the model is not changed.</p>
<h3>Indexing into the array:</h3>
<div ng-repeat="name in names">
Value: {{names[$index]}}
<input ng-model="names[$index]">
</div>
<p class="muted">Type one character, and the input field loses focus. However, the binding appears to be working correctly.</p>
</body>
ng-repeat fiddle. Main problem is angular's behaviour for three different versions. I understand that ng-repeat creates a new scope for each array item, I suppose (not sure) it tracks only array reference and it's size (so, array items shouldn't cause $digest loop on change), but, what we've got here (watch just first example without $index using): for version 1.0.3 < we have expected behaviour: changing 'name' property with input will only change ngModelController's value (scope inheritance) and this is fair, 1.1.1 - well, what's going on there: we don't even get new values inside of input and! we don't rerender our items cause we don't lose the focus (and i really don't understand why $digest loop fires value replacement for this input as Artem has said), third version - 1.2.1: changing input values also changes 'name' value in the outer scope (as I understand ngModelController should inherit a scope created by ng-repeat directive). So, what really happens (and why) in all three examples?
Using Angular latest version (1.2.1) and track by $index. This issue is fixed
http://jsfiddle.net/rnw3u/55/
<div ng-repeat="name in names track by $index">
Value: {{names[$index]}}
<input ng-model="names[$index]">
</div>

Categories