【问题标题】:Angular Directive $watch not respondingAngular 指令 $watch 没有响应
【发布时间】:2016-03-10 01:17:59
【问题描述】:

我正在尝试同时使用 d3 和 angular。我已经设置了以下 d3 模块:

angular.module('DTBS.d3', [])
  .factory('d3Service', ['$document', '$q', '$rootScope',
    function($document, $q, $rootScope) {
      var d = $q.defer();
      function onScriptLoad() {
        // Load client in the browser
        $rootScope.$apply(function() { d.resolve(window.d3); });
      }
      // Create a script tag with d3 as the source
      // and call our onScriptLoad callback when it
      // has been loaded
      var scriptTag = $document[0].createElement('script');
      scriptTag.type = 'text/javascript'; 
      scriptTag.async = true;
      scriptTag.src = 'http://d3js.org/d3.v3.min.js';
      scriptTag.onreadystatechange = function () {
        if (this.readyState == 'complete') onScriptLoad();
      }
      scriptTag.onload = onScriptLoad;

      var s = $document[0].getElementsByTagName('body')[0];
      s.appendChild(scriptTag);

      return {
        d3: function() { return d.promise; }
      };
}]);

angular.module('DTBS.directives', [])
  .directive('d3Bars', ['d3Service', function (d3Service) {
    return {
      restrict: 'EA',
      scope: {},
      link: function(scope, element, attrs) {
        d3Service.d3().then(function(d3) {
          // d3 code goes here
          var svg = d3.select(element[0])
          .append("svg")
          .style('width', '100%');

          scope.render = function (data) {
            // remove all previous items before render
            svg.selectAll('*').remove();
            // If we don't pass any data, return out of the element
            if (!data) return;
            svg.selectAll('rect')
            .data(data).enter()
            .append('rect')
            .attr('height', 50)
            .attr('width', 50)
            .style('background-color', red)
          };

          // set up watch to see if button clicked; add rectangle with render func
          scope.$watch('data', function(newVals, oldVals) {
            console.log("hey")
            return scope.render(newVals);
          }, true);
        });
      }};
  }]);

在我的代码的主要部分,我有一个名为“DTBS.test”的模块。这个模块有一个表控制器,它有一个叫做“保存”的功能。我希望我的 d3 指令监视这个保存函数被调用,当它被调用时,应该调用渲染函数来向 svg 添加一个矩形。

angular.module('DTBS.test', [])
.controller('TableController', ['$scope', function ($scope) {
    var secondsToWaitBeforeSave = 3;
    $scope.table = {};
    //Table save function that clears form and pushes up to the parent
    $scope.save = function () {
      $scope.id++;
      $scope.table.id = $scope.id;
      $scope.table.attrs = [];
      $scope.addTable($scope.table);
      $scope.table = {};
    };

  }])

目前它正在监视不存在的“数据”更改 - 我的问题是它应该监视什么以及如何将测试模块控制器中的事件链接到 d3 模块指令中的监视设置?

【问题讨论】:

    标签: javascript angularjs d3.js svg


    【解决方案1】:

    我会创建一个中间数据服务,这样如果愿意,任何东西都可以操纵 D3 数据。该数据服务可以广播事件,您的指令可以捕获这些事件以重新呈现您的数据。

    .service('d3Data', ['$rootScope', function($rootScope) {
      var data = [];
      var emit = function(data) { $rootScope.$broadcast('d3:new-data', data); }
      var api = {
        get: function() {
          return data;
        },
        set: function(data) {
          data = data;
          emit(data);
          return data;
        },
        push: function(datum) {
          data.push(datum);
          emit(data);
          return data;
        }
      }
    
      return api;
    }])
    

    我创建了一些小辅助函数以供您使用。您的指令不会有太大变化。只需添加一种捕获事件的方法而不是 watch。你可以把它放在你的scope.render函数下面。

    scope.$on('d3:new-data', function(e, data) {
       scope.render(data);
    });
    

    接下来,您将拥有想要与 D3 图表对话的控制器。

    .controller('TableController', ['$scope', 'd3Data', function ($scope, d3Data) {
      $scope.table = {};
      //Table save function that clears form and pushes up to the parent
      $scope.save = function(data) {
        data = angular.copy(data);
        d3Data.push(data);
      };
    }])
    

    模板将通过 ng-models 向save 函数提供数据。假设,您将在模板中使用 ng-model 输入一些输入,例如 table.widthtable.height,然后您可以在提交按钮中输入 ng-click="save(table)"。然后我们复制该数据,使其不再双向绑定到表单。并使用我们新的 d3Data 服务推送数据,该服务将广播一条消息,该指令将捕获并更新数据。

    我创建了一个简单的 Plunk 来演示这一点。 http://plnkr.co/edit/VMRNvZ?p=preview

    【讨论】:

    • 这正是我一直在寻找的,而且效果很好!非常感谢你,工作人员也很棒
    【解决方案2】:

    我看到的一个大问题是data 变量仅在render 函数中可用。您在 render 之外呼叫 $watch,其中 dataundefined

    一个简单的解决方案如下:

    scope.data;
    scope.render = function (data) {
         scope.data = data;
         // some code here
    }
    
    scope.$watch('data', function(newVals, oldVals) {
    }
    

    【讨论】:

    • 我不同意这一点。最好将渲染作为纯函数(如原始示例所示)。使他们的示例工作唯一需要做的就是在链接函数的几乎任何地方添加scope.data = [ // data here ]
    猜你喜欢
    • 2016-06-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-07-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-05-06
    相关资源
    最近更新 更多