Maintaining an array of ints representing option indices in Angularjs - javascript

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">

Related

Angular 4 - Scroll To item in list based on search input

I can do this with custom javascript but was wondering if there is anything built into Angular 4 that I might be able to make use of. I have a list of checkboxes that can be scrolled through and a search input above. I want to be able to jump to a section in the list as a user types in the search input. My HTML:
<!-- Sold To -->
<div class="col-md-12 input-container">
<div class="checkbox-group">
<input type="search" placeholder="Jump to Soldto...">
<div class="checkbox-wrap">
<mat-checkbox *ngFor="let option of soldToOptions">
{{ option }}
</mat-checkbox>
</div>
</div>
</div>
And a sample list of the array that I am looping through for the checkboxes (there will actually be about 300 entries in this array)...
soldToOptions = [
'1028341000-MITSUBISHI WONOKE MENTOR-DALLAS',
'1018551000-ADVANCE STARTER TEST-CINCINNATI',
'1030591000-AMERICAN JOYRIDE CARTHAGE-SAN FRANCISCO',
'1023221000-TESTING GENERAL OAKLAND-OAKLAND'
];
I will first need to order these alphabetically based on the the first part of the string after the initial '-'. Just wondering if there is anything built in to Angular 4 that would allow me to match the user's input string to the value from the array and jump to that section in the scrollable checkboxes. I strictly do not want to filter the list, just move the the proper place in the list. Any help is much appreciated.
ngx-page-scroll should be able to meet your needs. You would just need a way to dynamically add the markup to identify each element generated.

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 Data Binding Breaks ngRepeat + Strange Behavior

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/

How to show a message when filter returns nothing in ng-repeat - AngularJS

I would like to show one div(message) when no items returned on filter(ng-repeat).
Filter is happening based on select box (ng-model). And there are no results for some select options, at that time user should be able to read a message at same place.
Can I use ng-show/hide here? How?
Thanks,
You can also save your filtered array in a variable and then use that variable inside the ng-show expression:
<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>
You can see it in action in this plunker.
You can get the size of array returned by filter using something like
{{(data|filter:query).length}}
You can use this expression for ng-show expression. I am not sure how performant would it be.
There's a simpler solution using just the standard syntax of ngRepeat.
As stated in the official documentation, the ngRepeat accepts an alias for the collection even if filter are applied to it:
<div ng-repeat="model in collection | filter:search | orderBy: 'id' as filtered_result track by model.id">
{{model.name}}
</div>
Note: I added ad additional filter to the example copied from the documentation to have a more complete example.
In this example you can simply use:
<div class="alert alert-info"
ng-show="filtered_result.length == 0">
No items in the collection!
</div>
I tested it with AngularJS 1.5.0, I can't tell when this feature was introduced.

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