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>
  • 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` – New Dev Mar 13 '15 at 05: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. – Oleksii Chekulaiev Mar 13 '15 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 – New Dev Mar 13 '15 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. – Oleksii Chekulaiev Mar 13 '15 at 15:07
  • Doing this inside a service *is* the easier way – New Dev Mar 13 '15 at 15:08
  • Thanks for trying to help. I agree that it is better to do it inside service but it does not solve initial issue. Even if I create setter inside the service `$scope` will not auto-update bound elements after unload-load. I actually found an answer that suits me. Thanks for your time and regards! Your comments actually put me to the right googling route. – Oleksii Chekulaiev Mar 13 '15 at 15:47

1 Answers1

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.

Community
  • 1
  • 1