4

please pay attention this is the opposite of 99% of the related questions on stackoverflow.

Mi problem is: I have a dependent select, when the 'Master' select changes, the 'Slave' view gets updated but NOT it's model. WEIRD.

Example code:

<!DOCTYPE html>
<html ng-app>

<head>
  <script src="https://code.angularjs.org/1.3.5/angular.js"></script>
</head>

<body>
  <select ng-model='master'>
    <option value='m1'>Master 1</option>
    <option value='m2'>Master 2</option>
  </select>
  <select ng-model='slave'>
    <option value='Slave 1 selected' ng-selected='master == "m1"'>Slave 1</option>
    <option value='Slave 2 selected' ng-selected='master == "m2"'>Slave 2</option>
  </select>
  {{ slave }}
</body>

</html>

When changing the Master select, you can see the select changing but not the printed value for the binding {{ slave }}.

Link to plunker: http://plnkr.co/edit/LjaKgTTfBlczkGuCIhZ1

Daniel
  • 1,287
  • 12
  • 12
  • 2
    My guess is this happens because ng-selected isn't aware of the model at all, it simply sets the selected attribute, and it doesn't trigger any value changes, so the model doesn't get updated. – Rytmis Dec 09 '14 at 20:04
  • Why don't you use ng-options? ng-selected does not update ng-model – PSL Dec 09 '14 at 20:15
  • Are you looking for something like this? http://stackoverflow.com/questions/25413071/how-to-handle-cascading-chained-dropdowns-using-multiple-ng-options – PSL Dec 09 '14 at 20:24
  • @Daniel, did any of the answers below addressed your question? If so, please accept the appropriate answer. – New Dev Dec 12 '14 at 01:47
  • Wow, stackoverflow should really send emails… I never got notifications for all this! I'll be accepting right away… – Daniel Jan 21 '15 at 16:39

3 Answers3

1

@Shomz's answer is one way to go, although the replace seems a bit like a hack.

The reason that slave is not updated is because Angular is careful about changing the ViewModel without developer's knowledge. Typically, the ViewModel should be changed either by the controller in response to an external event or by the View - in response to user action. In this case, the change would have been in response to selected property being changed on the slave seleced, so Angular doesn't change slave.

Another way to think about it is: How would slave be updated in response to a change in master if you didn't have the View (i.e. <select>), such as the case with unit-testing the controller.

The proper way to implement this is, in fact, via a $watcher - so I disagree on this point with @Shomz.

I would also suggest to evaluate why you need both master and slave to be shadowing each other. If this is the case, you should then use the same ng-model:

<select ng-model="master2" 
       ng-options="val.k as val.v for val in [{k: 'm1', v: 'Master1'}, {k: 'm2', v: 'Master2'}]">
</select>
<select ng-model="master2" 
       ng-options="val.k as val.v for val in [{k: 'm1', v: 'Slave 1'}, {k: 'm2', v: 'Slave 2'}]">
</select>   
New Dev
  • 48,427
  • 12
  • 87
  • 129
  • yeah. i'd just go with ng-options too. – PSL Dec 09 '14 at 20:21
  • By the watcher part, I meant writing specific `scope.$watch('master'...` thing, that would be really bad. And I said that replace was pure laziness. :) The point being that ng-selected only cares about DOM. – Shomz Dec 09 '14 at 20:24
  • @Shomz, why? If `slave` does some conditional shadowing of `master`, I would say that this _is_ the way. If it's always the same, then it should be the same ViewModel property – New Dev Dec 09 '14 at 20:26
  • That's the problem, we can only guess what this thing is supposed to do. – Shomz Dec 09 '14 at 20:27
  • @Shomz, agreed - but I took an (educated) guess :) – New Dev Dec 09 '14 at 20:31
  • Hello, sorry about the delay, I never got any notification. The shadowing cannot use the same model because what I'm doing is providing a sensible default for the slave according to what master value has been selected, but I want the user to be able to override the slave AFTER if needed. So, the same model doesn't apply. The ng-options messed with key/value pairs, I tried all combos of `x as y for z in whatever`… – Daniel Jan 21 '15 at 16:45
1

Angular updates the view based on model changes and the model based on user interactions with the view. By changing the selected option manually using ng-selected, angular has not seen the user select a new option and therefore does not update slave. And since slave has not changed since it last set the selected option on the control it will not update the selected option. I don't think you should use ng-model and ng-selected together. I think this makes total sense and is not a bug in angular, ng-selected just isn't meant to be used that way. Think about this code:

$scope.buttonClicked = function () {
  $scope.slave = "Slave 1 selected";
  $scope.master = "m2";
};

What should the value of slave be and which option should be selected? You have set both options in code, what should angular do to the html select? Should it ignore your setting of the 'slave' value? Should it ignore the ng-selected attribute that says it should select slave 2 if master is m2? Let's say you set master to m1, this causes ng-selected to make the selected option slave 1. What if the user then changes the option to slave 2? The ng-selected option is then incorrect.

If you want to do a one-time setting of the slave value when the master is changed, you should create a watch to run code when the value changes:

$scope.$watch('master', function(newValue, oldValue) {
  if (newValue == 'm1') {
    $scope.slave = 'Slave 1 selected';
  } else if (newValue == 'm2') {
    $scope.slave = 'Slave 2 selected';
  } else {
    $scope.slave = undefined;
  }
});
Jason Goemaat
  • 28,692
  • 15
  • 86
  • 113
  • I'm accepting this because it ONLY has the correct answer but @new-dev also was right stating: 'The proper way to implement this is, in fact, via a $watcher' – Daniel Jan 21 '15 at 16:49
0

The Angular approach to this would be to change the slave model directly, not just the DOM (ng-selected basically just sets the selected attribute if it evaluates to true, nothing else). Technically, you can hack this by adding a watcher that will also update the model, but rewriting it sounds like a much better idea.

Here's an example, it's a bit sloppy, but replace was the quickest thing I could do without actually writing some JS (but you get the idea):

  <select ng-model='master' ng-change="slave = master.replace('m', 's')">
    <option value='m1'>Master 1</option>
    <option value='m2'>Master 2</option>
  </select>
  <select ng-model='slave'>
    <option value='s1' >Slave 1</option>
    <option value='s2' >Slave 2</option>
  </select>

http://plnkr.co/edit/WT7vb7yvtcvaUPUwGXdI?p=preview

Shomz
  • 37,421
  • 4
  • 57
  • 85
  • 1
    I am sure the original question is not just as simple as replacing m and s.. :D – PSL Dec 09 '14 at 20:16
  • I am as well, but we don't know what exactly OP is trying to do (why two models?), replacing was my way of avoiding the creation of a mapper object. :) – Shomz Dec 09 '14 at 20:20
  • Exactly, ng-options with an object mapping masters to slaves (don't like inlining it). – Shomz Dec 09 '14 at 20:22
  • I couldn't make my head around ng-options, it just messed with key/values in any order I gave it. I would love to see an example of the values given implemented with ng-options. I ended up using ng-change calling a function that updated the slave. But a mapper $watcher seems the correct way. – Daniel Jan 21 '15 at 16:53