0

I simplified my actual code but the problem is the same. After you click Start counter anonymous function in "First controller" starts changing data inside myService. First it works ok. But after switching to "Second controller" Angular stops auto-updating value of <span ng-bind="getData().data"> even if you navigate back to "First controller".

However if you switch controllers back and forth a bit you can see that span is updating on controller load so data is changing and controllers can access it. It's just Angular not auto-tracking it anymore.

How can I make Angular to start tracking changes and update element after switching controllers again?

<html>
<head>
<script src="https://code.angularjs.org/1.4.0-beta.4/angular.min.js"></script>
<script src="https://code.angularjs.org/1.4.0-beta.4/angular-route.min.js"></script>
</head>
<body>

<div style="padding: 0 0 20px 0">
    <a href="#/first">First controller</a> | <a href="#/second">Second controller</a>
</div>

<div ng-app="myApp">
    <div ng-view></div>

    <script type="text/ng-template" id="temp1.html">
        Data: <span ng-bind="getData().data"></span>
        <br/>
        <button ng-click="start()">Start counter</button>
    </script>

    <script type="text/ng-template" id="temp2.html">
        Data: <span ng-bind="getData().data"></span>
    </script>
</div>  

<script type="text/javascript">
    var myControllers = angular.module('myControllers', []);

    myControllers.factory('myService', [function() {
        return { data: 0 };
    }]);

    myControllers.controller('myController1', ['$scope', 'myService', function($scope, myService){
        $scope.getData = function() {
            return myService;
        }

        $scope.start = function() {
            setInterval(function() {
                myService.data++;
                $scope.$apply();    
            }, 1000)
        }
    }]);

    myControllers.controller('myController2', ['$scope', 'myService', function($scope, myService){
        $scope.getData = function() {
            return myService;
        }
    }]);

    var myApp = angular.module('myApp', [
      'ngRoute',
      'myControllers'
    ]);

    myApp.config(['$routeProvider',function($routeProvider) {
        $routeProvider.
            when("/first",{ controller: "myController1", templateUrl: "temp1.html"}).
            when("/second", {controller: "myController2", templateUrl: "temp2.html"}).
            otherwise({redirectTo : "/first"});
    }]);

</script>

</body>
</html>
6
  • Why are you setting the interval in the controller, and not in the service itself? Also, you should use $interval, rather than setInterval to avoid having to do $scope.apply Commented Mar 13, 2015 at 5:40
  • I'm doing setTimeout to emulate what's happening in my real application. I'm writing node-webkit app and instead of setTimeout there really is a child_process.on('data', callback) handler. Using $timeout solves the problem in this case but I really need more generic case that would work with any callback function. Note: I can append another $interval to the Controller1 to would forcibly call $scope.$apply() every 100 msec no matter what but that looks ugly. I think I need to properly reference $scope from callback function so that it would target current $scope instance all the time. Commented Mar 13, 2015 at 13:06
  • what exactly is the nature of child_process.on()? If it's a listener, you could re-subscribe. I would also encourage you to use a service and start/end listeners there, and not rely on a controller life cycle Commented Mar 13, 2015 at 13:13
  • Child process is a real child process running in background. child_process.stdout.on() is an event listener triggered when child process spits out something into stdout. Re-subscribing may work. Thanks I'll think about it though it's gonna take lot's of additional code and I thought there should be an easier way. Commented Mar 13, 2015 at 15:07
  • Doing this inside a service is the easier way Commented Mar 13, 2015 at 15:08

1 Answer 1

0

So I actually found an answer here: Passing data between controllers using service and confusion with using [ '$scope' , 'service' , function($scope, service){}] (see comment from Anthony Chu Feb 17 '14 at 6:12). The trick is in using $rootController to alert all other controllers via event to update data from service. So anonymous function will run in background without having reference to the new $scope but new $scope will receive messages from it via $rootScope event down propagation.

I've appended $root.$broadcast(...) to anonymous function and appended respective $scope.$on(...) to both controllers. So controllers code now looks like this:

myControllers.controller('myController1', ['$scope', 'myService', '$interval', '$rootScope', 
  function($scope, myService, $interval, $rootScope) {
    $scope.getData = function() {
      return myService;
    }

    $scope.$on('myupdate', function() {
      if (!$scope.$$phase) { //checks that Angular is not updating scope right now
        $scope.$apply();
      }        
    });

    $scope.start = function() {
      setInterval(function() {
        myService.data++;
        $rootScope.$broadcast('myupdate');
      }, 1000)
    }
}]);

myControllers.controller('myController2', ['$scope', 'myService',
  function($scope, myService) {
    $scope.getData = function() {
      return myService;
    }

    $scope.$on('myupdate', function() {
      if (!$scope.$$phase) {
        $scope.$apply();
      }        
    });
}]);

Fully working code example.

Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.