6

In a parent controller scope, I have defined selectedItem which is set to 'x'. Then in the child scope, I have defined selectedItem using ngModel:

<div ng-app>
  <div ng-controller="CtrlA">
       <div ng-controller="CtrlB">
         <select ng-model="selectedItem" ng-options="item for item in items">
         </select>
      </div>
  </div>
</div>

function CtrlA($scope) {
    $scope.selectedItem = 'x';
    $scope.items = ['x', 'y'];
}

function CtrlB($scope) {}

When the page is loaded, the selectedItem is properly set to 'x' as expected. When I select 'y', selectedItem in CtrlB $scope gives 'y' as expected.

But when I inspect $scope.selectedItem in CtrlA scope (using AngularJS's batarang), it gives 'x' .

jsFiddle: http://jsfiddle.net/sudhh/GGKjp/2/

Preview page: http://fiddle.jshell.net/sudhh/GGKjp/2/show/light/ (for inspecting with angularjs batarang)

Why is $scope.selectedItem in CtrlA scope not getting updated to 'y'? What is the explanation?

I prefer not to use events or rootScope to update selectedItem in parent scope (for learning purposes).

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
sudhakar
  • 181
  • 1
  • 4
  • 12
  • 1
    Be sure to read Stack Overflow question *http://stackoverflow.com/questions/14049480* It gives a good in-depth overview of scope inheritance in AngularJS. – Martijn Jul 15 '13 at 07:19

2 Answers2

7

If you try to bind to a primitive declared on parent scope, then the selectedItem in child scope will effectively shadow the property of the same name in the parent scope.

In this case there are 3 choices

  1. define objects in the parent for your model, then reference a property of that object in the child: ref.selectedItem
  2. use $parent.selectedItem (not always possible, but easier than 1. where possible)
  3. define a function on the parent scope, and call it from the child, passing the primitive value up to the parent (not always possible)

More about it on https://github.com/angular/angular.js/wiki/The-Nuances-of-Scope-Prototypal-Inheritance

You can find the updated fiddle using the first approach at http://jsfiddle.net/sudhh/XU2rP/1/

function CtrlA($scope) {
  $scope.items = ['x', 'y'];
  $scope.ref = {
    selectedItem: 'x'
  };
}
sudhakar
  • 181
  • 1
  • 4
  • 12
  • Indeed, I extended your code a bit in this [jsFiddle](http://jsfiddle.net/asgoth/fDhcr/) by using watches which log to console. Only the watch in B gets triggered. – asgoth Jan 09 '13 at 12:09
0

I've noticed in similar cases that AngularJS does not watch selectedItem properly. The only way I found is to initialize selectedItem with an entry from the items array. Try the following:

function CtrlA($scope) {
    $scope.items = ['x', 'y'];
    $scope.selectedItem = $scope.items[0];
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
asgoth
  • 35,552
  • 12
  • 89
  • 98
  • Still doesnt work. If you select "y" from the dropdown & inspect the scope, CtrlB gives 'y' as expected but CtrlA still shows selectedItem as 'x' – sudhakar Jan 09 '13 at 10:59
  • 1
    Angular is watching `selectedItem` properly. The issue is that there are two `selectedItem` properties -- one in the parent scope and one in the child scope. Angular is watching both of them, properly, but independently. Because of the way JavaScript prototypal inheritance works, the child scope will only see its property, and the parent only its property. What you want is to have the child scope use the property that already exists in the parent scope, and not create a new property. (I did see that @sudhakar already found the answer, I just wanted to clarify what is going on.) – Mark Rajcok Jan 09 '13 at 15:49