javascriptangularjsangularjs-rootscopejavascript-inheritance

AngularJS: changing $rootScope variable doesn't change first selected option in ng-model


I have a specific problem which I can't seem to crack.

In html I have following code:

<select ng-model="$parent.proizvod"  ng-options="i.id_proizvod as i.proizvod for i in proizvodiServer">
   <option>Select</option>
 </select>

And in my controllers:

  $rootScope.proizvod = '1';

So when the page first loads, the first option is selected. If I change the $rootScope.proizvod = '1'; to $rootScope.proizvod = '3'; It will load the third option.

Now, when you select some other value, it will store it in $rootScope.proizvod, but later, in some function, I want to reset that $rootScope.proizvod back to '1' and it doesn't seem to be working.

What am I missing here?

P.S. without $parent.proizvod, my select doesn't even change the $rootScope.proizvod value.

EDIT: Here's the other fucntion:

$rootScope.upisPodataka = function() {
      setTimeout(function () {
      $rootScope.proizvod = '1';
      $scope.$apply();
       }, 2000);
      $state.go('app.podesenost_grilla', {}, {
        reload: true
      });

upisPodataka function is called on a click of a button.


Solution

  • Using ng-model='$parent.proizvod' is not guaranteed to update $rootScope in all circumstances.

    New AngularJS developers often do not realize that ng-repeat, ng-switch, ng-view, ng-include and ng-if all create new child scopes, so the problem often shows up when these directives are involved.

    AngularJS Wiki - Understanding Scopes

    If the ng-model sets the value in a child scope. The child scope gets its own property that hides/shadows the parent property of the same name. This is not something AngularJS is doing – this is how JavaScript prototypal inheritance works. Read the Wiki.

    So the question becomes should it be $parent.$parent.proizvod? Or maybe $parent.$parent.$parent.proizvod? Do it by trial and error?

    A more robust approach is to use the ng-change directive to update the variable.

    <select ng-model="vm.proizvod" ng-change="vm.update()"
            ng-options="i.id_proizvod as i.proizvod for i in proizvodiServer">
       <option>Select</option>
    </select>
    
    vm.update = function () {
        $rootScope.proizvod = vm.proizvod;
        console.log($rootScope.proizvod);
    });
    

    Instead of using $rootScope, consider using a custom service to store the variables.

    $rootScope exists, but it can be used for evil

    Scopes in AngularJS form a hierarchy, prototypically inheriting from a root scope at the top of the tree. Usually this can be ignored, since most views have a controller, and therefore a scope, of their own.

    Occasionally there are pieces of data that you want to make global to the whole app. For these, you can inject $rootScope and set values on it like any other scope. Since the scopes inherit from the root scope, these values will be available to the expressions attached to directives like ng-show just like values on your local $scope.

    Of course, global state sucks and you should use $rootScope sparingly, like you would (hopefully) use with global variables in any language. In particular, don't use it for code, only data. If you're tempted to put a function on $rootScope, it's almost always better to put it in a service that can be injected where it's needed, and more easily tested.

    Conversely, don't create a service whose only purpose in life is to store and return bits of data.

    — AngularJS FAQ


    Use $timeout instead of window.setTimeout

    $rootScope.upisPodataka = function() {
          //setTimeout(function () {
          $timeout(function () {
             $rootScope.proizvod = '1';
             //$scope.$apply();
          }, 2000);
          $state.go('app.podesenost_grilla', {}, {
            reload: true
          });
    };
    

    The $timeout service wraps windows.setTimeout and integrates it with the AngularJS framework and its digest cycle. Then $apply() is not needed.

    In addition, $timeout.flush() can be used when testing.