Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

angular ng-options directive ends up selecting multiple items

Tags:

html

angularjs

In AngularJS page I have a dropdown that lists buildings (facilities):

<select ng-model="selectedFacility"
   ng-options="facility.abbr for facility in facilities track by facility.id">
</select>
<label>{{selectedFacility.name}}</label>

Seems to be working. The value in the dropdown shows what user selects (abbreviation), and the text in the label shows corresponding long name. Good.

However, elsewhere I pass $scope.selectedFacility.id to $http call and I noticed that it is always 1, regardless what building is actually selected. I looked deeper (in Chrome F12), and I see that all dropbox items that I selected at some point have selected="selected" attribute:

<option value="1" label="Engineering" selected="selected">Engineering</option>
<option value="2" label="Biology" selected="selected">Biology</option>    
<option value="3" label="CompSci">CompSci</option>    
<option value="4" label="Admissions" selected="selected">Admissions</option>    
<option value="5" label="Security">Security</option>

As is clear from the output above, I selected items 1, 2, and 4 at some point. My guess is that since first item was selected initially, it ends up always having "selected" attribute, and therefore selectedFacility.id=1. But how do I tell Angular to remove selected attribute? It is a regular dropdown, without any way to select multiple items - yet multiple items get marked as selected

UPDATE. It looks like it is important that the controller has the following:

$scope.selectedFacility= {};
$scope.facilities = facility.getFacilities().query(function(facilities) {
  $scope.selectedFacility=facilities[0];
});

and facility is provider service that retrieves facility information from the server:

  service.getFacilities = function() {
    return $resource(url);
  };

If I start using $scope.selectedFacility.imeanit=facilities[0]; and selectedFacility.imeanit in ng-model, it works as expected

like image 342
Felix Avatar asked Sep 13 '26 15:09

Felix


1 Answers

The Problem

I was able to recreate your issue here.

I setup a watch on $scope.selectedFacility, to see when the model actually updates.

$scope.$watch('selectedFacility', function(newVal, oldVal) {
  console.log(newVal);
  console.log(oldVal);
});

The watch gets called once at the start:

[Log] null (main.js, line 17)
[Log] null (main.js, line 18)

Then once more inside the query() callback:

[Log] Resource (main.js, line 17)
    abbr: "Eng"
    id: 1
    name: "Engineering"
    __proto__: Resource
[Log] null (main.js, line 18)

But then never again, even when changing the selected option in the <select>.

The reason why has to do with how JS prototypal inheritance works, and the implications of that with 2-way bindings & Angular's ability to do dirty-checking when child scopes are involved (which are here, thanks to ng-options). This YouTube video explains it well.

The Fix

If you instead make facilities an object whose members are the list of Resources and the selected Resource, then it will work. Here's another plunker showing a working example (relevant details below):

JS:

$scope.facilities = {
  selected: null,
  all: []
};

$scope.facilities.all = Facilities.query(function(list) {
  $scope.facilities.selected = list[0];
});

HTML:

<select ng-if="facilities.all.length"
        ng-model="facilities.selected"
        ng-options="facility.abbr for facility in facilities.all track by facility.id">
</select>
<label ng-if="facilities.selected">{{facilities.selected.name}}</label>

(strictly speaking, only the ng-model (i.e. facilities.selected) needs to be a property-of-a-property-of-$scope, but I thought the above structure was cleaner).

like image 74
tavnab Avatar answered Sep 16 '26 06:09

tavnab



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!