Oliver Oliver - 5 months ago 8
AngularJS Question

Changes to selected element in scope won't be persisted in array of available elements

I'm using angularJS 1.5.6 and try to update a value by using a combo box within an object that had already being selected through another combo box. But after switching the source object these changes are lost.

For a better understanding try this JSFiddle.

Within the above example the first combo box just selects an item out of a list of availables:

<select data-ng-model="currentElement"
data-ng-options="element.name for element in elements track by element.id"
data-ng-change="updateDeeperOption()">
</select>


The second combo box works on a second list of available options:

<select data-ng-model="deeperOption"
data-ng-options="option.name for option in availableOptions track by option.value"
data-ng-change="writeDeeperOptionToCurrentElement()">
</select>


The two method, which are being called whenever a change happens are simply updating the data in both directions:

$scope.updateDeeperOption = function() {
$scope.deeperOption = $scope.availableOptions.filter(function (option) {
return option.value === $scope.currentElement.going.deeper.to.myOption; })[0];
};

$scope.writeDeeperOptionToCurrentElement = function() {
$scope.currentElement.going.deeper.to.myOption = $scope.deeperOption.value;
};


The problem comes from
writeDeeperOptionToCurrentElement()
. It updates the current element (you can see it within the fiddle), but if you switch to another element and back again, the changes are lost.

Any idea, what I made wrong? I think, I'm still missing some fundamental understanding about when objects are copied or just referenced within AngularJS. So any explanation or links would be helpful.

Answer

Update:

I isolated the actual problem here and found that if you remove the track by from your first select then the currentElement object gets updated by reference. See: https://jsfiddle.net/tbzggyg1/4/

So instead of this:

<select data-ng-model="currentElement"
            data-ng-options="element.name for element in elements track by element.id"
            data-ng-change="updateDeeperOption()">

Try this:

<select data-ng-model="currentElement"
            data-ng-options="element.name for element in elements"
            data-ng-change="updateDeeperOption()">

And here is why: https://docs.angularjs.org/api/ng/directive/ngOptions

... is to use a track by clause, because then ngOptions will track the identity of the item not by reference, but by the result of the track by expression. For example, if your collection items have an id property, you would track by item.id.


Old answer:

It looks like your '$scope.currentElement' object is actually just getting a copy reference from the array, so when you make modifications it doesnt really update the original array (which is what your dropdowns bind to). I tried this and it worked:

  $scope.writeDeeperOptionToCurrentElement = function() { 
      // $scope.currentElement.going.deeper.to.myOption = $scope.deeperOption.value; // old code

      var elementRef = $scope.elements.filter(isMatchingElement)[0];
      elementRef.going.deeper.to.myOption = "" + $scope.deeperOption.value;
  };
  function isMatchingElement(e) {
    return e.id === $scope.currentElement.id;
  }

Also, add this to your template so you can see the entire picture of whats happening:

<tt>{{currentElement}}</tt>
<hr>
<tt>{{elements}}</tt>

Link to fiddle forked: https://jsfiddle.net/t2dvm0a2/