简体   繁体   中英

AngularJS Data Binding - Watch cycle not triggering

Assuming the following JSFiddle: https://jsfiddle.net/pmgq00fm/1/

I want my NVD3 chart to update in real time, based on the setInterval() on line 39 that updates the data the directive is bound to. Here are quick pointers on the architecture and code:

  • The directive is within the scope of the ThisController
  • The data is bound bi-directionally using the '=' in the directive
  • An update function is supposed to be called on the chart whenever the directive detects a change in the data during the $watch cycle.
  • The $watch is a deep watch, so changes in values should be detected
  • An interval was set in the directive to print in the console the changes in value. The controller prints the updated array whenever the updateFnc is called.
  • The values in the data in the directive never matches the data in the controller for some reason I ignore.
  • The values are modified both in the controller and in the directive.
  • The $watch in the directive is only called on execution
  • The code is based on this tutorial and respects all of the steps necessary for data binding explained in it.

To debug, run the JSFiddle and open the console to see the objects and debug prints.

HTML:

<div id="stats-container" ng-app="myApp" ng-controller="ThisController">
    <div id="nvd3-test" nvd3-discrete-bar data="data.graph">       
</div> 

JS:

myControllers.controller('ThisController', ['$scope', function ThisController($scope){
      $scope.data = { graph : [ ... ] };

      updateFnc = function(data){
        for(var i = 0; i < data[0].values.length ; i++){
          data[0].values[i].value = Math.random();
        }
        console.log(Date.now()/1000 + ": In Controller");
        console.log(data);
      };

      setInterval(function(){
        updateFnc($scope.data.graph);
      }, 1000);
 }]);

myServices.factory('NVD3Wrapper', ['NVD3S', 'D3S', '$q', '$rootScope', function NVD3Wrapper(nvd3s, d3s, $q, $rootScope){
  return {
    nvd3: nvd3s,
    d3: d3s,
    discreteBarChart : function(data, config){
        var $nvd3 = this.nvd3,
            $d3 = this.d3,
            $nvd3w = this, //In order to resolve the nvd3w in other scopes.
            d = $q.defer(); //Creating a promise because chart rendering is asynchronous
        $nvd3.addGraph(function() {
          ...
        });
        return {
          chart: function() { return d.promise; } //returns the chart once rendered.
        };
    },
    _onRenderEnd: function(d, chart){
      $rootScope.$apply(function() { d.resolve(chart); });
    },
  };
}]);

myDirectives.directive('nvd3DiscreteBar', ['NVD3Wrapper', function(nvd3w){
  return {
    restrict: 'EA',
    scope: {
      data: '=' // bi-directional data-binding
    },
    link: function(scope, element, attrs) {
      var chart,
          config = {
            target: element,
          };
      var wrapper = nvd3w.discreteBarChart(scope.data, config);
      wrapper.chart().then(function(chart){
        scope.$watch(function() { return scope.data; }, function(newValue, oldValue) {
          console.log(Date.now()/1000 + ": In Directive $watch");
          if (newValue)
              chart.update();
        }, true);
      });
      //For testing
      setInterval(function(){ console.log(Date.now()/1000 + ": In Directive"); console.log(scope.data); }, 1000);
    }
  };
}]);

Any help would be greatly appreciated! Thanks a lot!

EDIT : New JSFiddle with Andrew Shirley's answer: https://jsfiddle.net/pmgq00fm/3/

Add the line

$scope.$apply()

to the update function. This is because when a variable is updated in the javascript, outside of the common angular functions, angular isn't aware of the change and therefore doesn't waste any effort refreshing the DOM. This forces a digest cycle which should refresh what you see.

EDIT: It is pretty important to actually understand why you're actually using a scope.apply and I don't feel I've described it incredibly well. Here's an article that does that much better than I have.

http://jimhoskins.com/2012/12/17/angularjs-and-apply.html

I point this out because you need to be aware that if you ARE within a function that's closely tied to angular (eg something called by ng-click), then if you attempt to use a scope.$apply you will get javascript errors, because you'll already be in the middle of a digest cycle.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM