AngularJS ngOptions select list behaves inconsistently in IE 9 - angularjs

This jsFiddle hopefully demonstrates the problem I am facing with IE 9. I am roughly doing the same things I am doing in my real app. I have a list of stuff that I loop through using ngRepeat. Each item has a property that can be selected from a dropdown list which is rendered using ngOptions.
The problem in IE seems to be that selecting an item from the list jumps back to the previous item in the list. Chrome does not have this problem, the item selection is perfect. In the example, try selecting the value 'Imported' in the Fiddle - I could simply not do it in IE whereas it works perfectly in Chrome.
The only thing I can think of that could be a problem is the IDs for the dropdown which are currently just plain old string values like '20', '30' and '40'.
The code for ngOptions is
<select class="form-control" id="selProc" data-ng-model="currentPlant.ProcType" data-my-dropdown='Plants' data-ng-options="plant.SpecialProcurement as plant.Description for plant in Plants"></select>
currentPlant here is the looped variable.
The dropdown values are the following array:
var dropDownValues = [{
Plant: '2150',
SpecialProcurement: '20',
Description: 'External'
}, {
Plant: '2150',
SpecialProcurement: '30',
Description: '3rd Party'
}, {
Plant: '2150',
SpecialProcurement: '40',
Description: 'Imported'
}];
These dropdown values get populated in a custom directive's link function. In my real app the list gets fetched using $http.post() and the result is assigned to the dropdown variable using JSON.stringify. Regardless of whether this is good practice or not, the problem still persists. If the problem is because of the JSON.stringify, I would be keen to know. I have tried replacing that with a regular array assignment but the result is the same.
I'm stumped. Please help.

Related

Does ngFor directive re-render whole array on every mutation?

Let's say we have an array of items:
items = [
{ title: 'item 1'},
{ title: 'item 2'},
/* ... */
];
And there is a template that renders this array:
<ul>
<li *ngFor="let item of items">{{item.title}}</li>
</ul>
Wll angular2 rerender the whole array if I add/remove items via push/splice or will it only add/remove the markup for the corresponding items? If it does updates only, then is there any difference in mutation stategies -- should I prefer push/splice over array replacing? In other words, are these two approaches equivalent in term of rendering performance:
/* 1: mutation */
this.items.push({ title: 'New Item' });
/* 2: replacement */
var newArray = this.items.slice();
newArray.push({ title: 'New Item' });
this.items = newArray;
In addition to Gunter's answer, if you want to know which part of your UI is rendered/re-rendered you can with Chrome (even independent from any lib/framework) :
Open your debug panel
Menu (of debug panel) / More tools / Rendering
You should then see the following panel :
Toggle the Paint Flashing option on, and have some fun with your list.
If an area is flashing green, it has been painted / re-painted 👍.
EX : If you take the Plunkr in Gunter's answer : http://plnkr.co/edit/oNm5d4KwUjLpmmu4IM2K?p=preview and toggle the Paint Flashing on, add an item to the list and you'll see that previous items do not flash. (which means there's no repaint).
No , it re-renders only when the array itself is replaced by a different array instance.
update
Thanks to Olivier Boissé (see comments)
Even when a different array instance is passed, Angular recognizes if it contains the same item instances and doesn't rerender even then.
See also this StackBlitz example
If the used IterableDiffer recognizes and addition or removal at the beginning or in the middle, then an item is inserted/removed at that place without re-rendering all other items.
The animations demonstrated in Plunkers in answers of this question How can I animate *ngFor in angular 2? also demonstrate that. In fact this kind of animation was a driving factor to get this implemented this way (in addition to general optimization)

Select directive not selecting option when options are loaded asynchronously

I have a <select> directive whose options are loaded asynchronously. The variable that the ng-model attribute points to exists prior to the options being loaded. However, after the options are loaded the correct option is not selected. Instead, the select becomes blank. I've created a plunk to demonstrate this behaviour: http://plnkr.co/edit/KNunC6.
https://github.com/angular/angular.js/issues/9714#ref-pullrequest-53337829
seems like this is an angular core bug. you'd be more successful using ng-repeat and ng-checked I guess.
I think this fixes your issue. Here's a plunker. The problem was that you were initializing the ngModel before the promise was finished. The ngModel needs to be initialized after the list gets populated so angular can figure out which option is selected. Also, the ngOptions is tricky sometimes because angular assigns a unique id to every option and ignores the value attribute. I had this issue a couple of times also, nasty one!
There is a problem when the value part of the option has the same value as the label part.
In your case, value == id == 'Canada' and label == name == 'Canada' as well. If you change one of them to anything else, it will work. I am not sure why it is an issue, but just change and it will be ok.
Example: change the name from 'Canada' to 'Canada ':
[
{ id: 'Canada', name: 'Canada ' },
{ id: 'US', name: 'United States of America' }
]

AngularJS: how put correct model to repeated radio buttons

I think I have some sort of special code here as all I could google was "too simple" for my problem and it also didn't helped to come to a solution by myself, sadly.
I got a radio button group of 2 radios. I am iterating over "type" data from the backend to create the radio buttons.
My problem is the data binding: When I want to edit an object its "type" is set correctly, but not registered by the view so it doesn't select the desired option.
Follwing my situation:
Backend providing me this as "typeList":
[
{"text":"cool option","enumm":"COOL"},
{"text":"option maximus","enumm":"MAX"}
]
HTML Code:
<span ng-repeat="type in typeList track by type.enumm">
<input
type="radio"
name="type" required
ng-model="myCtrl.object.type"
ng-value="type">
{{type.text}}
</span>
Some Explanation
I don't want to use "naked" texts, I want to use some sort of identifier - in this case it is an enum. The chosen value shall be the entire "type", not only "type.text" as the backend expects type, and not a simple String.
So all I do with this is always a package thingy, the type.text is for like formatted/internationlized text etc.
A Pre-Selection works by setting this in the controller: this.object.type = typeList[0];
The first radio button is already selected, wonderful.
But why isn't it selected when editing the object. I made a "log" within the HTML with {{myCtrl.object.type}} and the result is {"text":"cool option","enumm":"COOL"}. The very same like when pre selecting. I already work with the same "technique" using select inputs, and it works fine. I also found some google results saying "use $parent because of parent/child scope". But 1) I didn't get that straight and 2) think it is not the problem here, as I use a controllers scope and not the $scope, or is this thinking wrong?
It might be explained badly, sorry if so, but I hope someone 1) get's what I want and 2) knows a solution for it.
Thank you!
If you're trying to bind to elements from an array, I believe you need to assign the actual elements from the array to your model property.
So this creates a new obj and sets it to $scope.selectedType (not what you want):
$scope.selectedType = {"text":"cool option","enumm":"COOL"};
whereas this assigns the first element of the array (which is what you want)
$scope.selectedType = $scope.typeList[0];
So to change the model, you can lookup the entry from the array and assign it to your model with something like this
$scope.selectedType = $scope.typeList.filter(...)
Here's a quick example of this approach http://plnkr.co/edit/wvq8yH7WIj7rH2SBI8qF

Initializing ngModel with data - AngularJS Bootstrap bs-select

I am maintaining a site that allows users to create a profile of sorts that will allow them to broadcast activities to a feed. I implement ng-grid to keep track of all the profiles that are created, and have created two buttons that allow users to create/edit these profiles. My only problem right now is, when users select a row on the grid and attempt to edit that specific row, the drop-down menu is not auto-populated with the data from ngModel.
This is the part of the form I am having trouble with:
<select ng-model="source.canSendTo" ng-options="value.name for value in sourceCanSendTo" data-style="btn" bs-select></select>
And within the controller, I have sourceCanSendTo defined as:
$scope.sourceCanSendTo = [ {"id":"abc", "name": "ABC"}, {"id":bcd", "name": "BCD"} ... ];
On row selection, I simply set source = the selected item, and console.logs show that all the data is there. The other parts of the form are being populated properly (mainly s), and console.log($scope.source.canSendTo) shows that the original data is there, it's just that select is defaulted to being blank...how would I go about trying to pre-select certain elements on the drop-down select I currently have?
For example, if the profile has 'abc', 'bcd' selected, how can I make it so that when I edit that profile, the drop down box shows 'abc,bcd' instead of just "Nothing Selected"?
Edit: I previously responded to a comment inquiring about bs-select, saying that it simply controlled some CSS elements of the drop down box - seems like this is completely incorrect after a quick google search when everything else led to dead ends. Does anyone have any idea how to properly initialize the model with data so that when I preload my form, the 'can send to' drop down select actually has the selected options selected, as opposed to saying "Nothing Selected"? Thanks in advance for all help!
As you are binding source.canSendTo to the name (value.name) of sourceCanSendTo then you just need to initially have an structure binding the names which had been saved, something like this:
source.canSendTo = ['abc', 'bcd']; //And all the selected values
So you need to construct your source.canSendTo property to this structure.
PS: If you show how you bring your data from the server, I can help you to construct the source.canSendTo property.
$scope.canSendTo must be initialized with a reference to the selected option.
var initialSelection = 0;
$scope.source = { canSendTo : [ {"id":"abc", "name": "ABC"}, {"id":bcd", "name": "BCD"} ... ] };
$scope.canSendTo = $scope.source.canSendTo[initialSelection];
Finally found out what was wrong with my code - seems like the data being stored in the model wasn't the same as what was in ngOptions, played around a bit with ngOptions and managed to get something that works. Working snippet of code:
<select ng-model="sendTo.name" ng-option="value.name as value.name for value in sourceCanSendTo" data-style="btn" multiple bs-select>
(Realized that the variable being used for ngModel was a fairly ambiguous in terms of naming convention, changed it)

How do I set up cascading dropdownlists in Angular?

I got an angular dropdown list with some options. Lets say we have a dropdown list with
[List]
A
B
C
D
E
F
If you choose A you will see another dropdownlist with:
[List]
A-1
A-2
A-3
A-4
And with B
[List]
B-1
B-2
So the second list depends on what you choose in the first list. So far i got this.
Html
<div class="" ng-show="signIn">
<select ng-model="myProject" ng-options="proj.title for proj in projects | unique:'title'"><option value="">-- choose project --</option></select>
<select ng-options="myProject.name"></select>
</div>
The problem is i cant use the binded variable in the first list.. What is the best way to achieve this.
This actually depends on your data structure, and how you retrieve it. I've mocked up an example of using a nested structure like this:
$scope.list = [
{
name:'A',
items: [
'A-1',
'A-2',
'A-3',
'A-4'
]
}, {
name: 'B',
items: [
'B-1',
'B-2'
]
}, {
name: 'C'
}
];
Here's the live example: http://jsfiddle.net/hRz8G/
See if you can use it, maybe you'll need to adjust your data structure. If that's impossible, post your data structure, we might be able to work out a solution.
Update
Try this example, and see if it fits your needs: http://jsfiddle.net/hRz8G/3/
It's traversing the projects list, and nests all the tasks inside of the project in a tasks-array.
[{
"projectId":1,
"title":"someTitle",
"user":"mike",
"tasks":[
{"taskId":1,"name":"Sales"},
{"taskId":3,"name":"Support"}
]
},
...
]
I hope that's kind of the solution you're looking for.
There's a filter defined, that keeps things tidy. Unfortunately it's kind of tightly bound to the project-object structure. If the id fieldname ('projectId') changes, you'll need to change the filter too, but that's kind of a problem I'm struggling to solve.
So I got the answer. I thought I knew the answer but i didn't.. I did everything the same and this time it worked =S.. Also thanks to you Fiddle I created my drop down list. I filtered the projects with 'myProject.title' but it didn't work. So I outputted the 'myProject' and it gave me this information back.
{"projectId":"1056","title":"someTitle","user":"mike","taskId":"3","name":"Design"}
So when i got this back i thought i filtered it with the whole json file. (which is not true..) But then i changed my select input to ng-change and called a function -> selectedProject(myProject). In that function i got the title of the project and i returned it back. In my second drop down list i changed it to 'filter:selectedProject' and it suddenly worked.. I was like huh? so i thought it would be faster if we just filtered the drop down list with the input of the first. So i filtered it with myProject.title and it also worked.. So i still don't know the answer but this is my code now. The controller code is just some $http.post function
HTML file
<div class='timesheet' ng-controller="timesheetController">
<div class="" ng-show="signIn">
<select ng-model="title" ng-options="proj.title for proj in projects | unique:'title'"><option value="">-- choose project --</option></select>
<select ng-model="taskId" ng-options="proj.name for proj in projects | filter:title.title"></select>
{{title.title}}{{taskId.name}}
</div>
</div>

Resources