将D3注入AngualrJS的正确惯例

时间:2015-12-01 00:07:47

标签: javascript angularjs d3.js

我看过只使用全局D3对象的指令,我还看到了通过在服务中返回来注入全局D3对象的指令,并且我看到了添加的指令D3脚本并返回在提供D3对象的脚本加载时解析的promise。

在注射服务中使用它似乎最有意义(参见示例1和2),但我不确定哪种方式更好。示例2将保证在运行任何代码之前已加载D3,但它似乎没有人这样做,而且它意味着您必须将整个指令包装在服务中,否则d3和创建的svg对象超出范围或可能未定义(参见示例2),但至少我认为编译器的承诺将始终首先解析,请参见示例3.

示例1:服务传递D3全局对象

.factory('D3Service', [,
    function () {

        // Declare locals or other D3.js
        // specific configurations here.

        return d3;
    }]);

示例2:服务将D3脚本添加到DOM并传递承诺

.factory('D3Service', ['$window', '$document', '$q', '$rootScope',
    function ($window, $document, $q, $rootScope) {

        var defer = $q.defer();

        var scriptTag = $document[0].createElement('script');
        scriptTag.type = 'text/javascript';
        scriptTag.src = 'https://d3js.org/d3.v3.min.js';
        scriptTag.async = true;
        scriptTag.onreadystatechange = function () {

            if (this.readyState == 'complete') {
                onScriptLoad();
            }
        }
        scriptTag.onload = onScriptLoad;

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

        //---
        // PUBLIC API
        //---

        return {
            d3: function () {
                return defer.promise;
            }
        };

        //---
        // PRIVATE METHODS.
        //---

        // Load D3 in the browser
        function onScriptLoad () {
            $rootScope.$apply(function () {
                defer.resolve($window.d3);
            });
        }
    }]);

示例3:使用编译添加SVG并不意味着链接中可以使用SVG,但至少编译的承诺将始终首先解析

        // Perform DOM and template manipulations
        function compile ($element, $attrs, $transclude) {

            var svg;

            // Callback provides raw D3 object
            D3Service.d3().then(function (d3) {

                // Create a responsive SVG root element
                svg = d3.select($element[0])
                .append('svg')
                .style('width', '100%');
            });

            // Return the link function
            return function($scope, $element, $attrs) {

                // Is svg undefined? 

                // Maybe? so have to wrap everything again in service
                D3Service.d3().then(function (d3) {

                   function render() {
                       // d3 and svg guaranteed to be available, but code gets really ugly looking and untestable
                   }
                });

                function render() {
                    // d3 and svg have to be passed in as they may not be available, but code is cleaner
                }
            };
        }

1 个答案:

答案 0 :(得分:4)

在遇到d3Angular问题时,我遇到了类似的问题。似乎有几种方法可以解决这个问题;每个都是可行的,但没有一个感觉光滑或自然。在其核心,d3Angular似乎只是两种截然不同的技术,而且它们不能在开箱即用的情况下很好地发挥作用。不要误解我,他们一起工作,但他们需要彼此热身。因此,我们可以在d3框架内为Angular提供一个游乐场。而且我相信这个游乐场应该是directive

但是关于返回promise的模块化d3Service方法(根据d3.js文件的加载):

angular.module('myApp.directives', ['d3'])
  .directive('barChart', ['d3Service', function(d3Service) {
    return {
      link: function(scope, element, attrs) {
        d3Service.d3().then(function(d3) {
          // d3 is the raw d3 object
        });
      }}
  }]);

尽管在ngNewsletter中详细说明了这一点,但使用将script标记直接写入DOM的服务似乎有点过分,因为它只能包含在index.html中与所有其他JavaScript文件。我的意思是,我们知道有directive使用此文件,为什么不故意加载它?似乎没有必要跳过篮球,只是:

<script src="/js/third-party/d3js/d3.min.js"></script>

然而,这种方法承诺确实提供了模块化 - 让我们说我们正在构建多个应用程序并且每个需要d3,然后是的,能够非常轻松地注入我们的d3应用程序级别的模块很棒。但是,你总是不得不等待这个承诺,即使我们知道它会在初始加载后立即解决,你仍然需要解决它。在任何使用它的指令或控制器中。总是。长号。

正如我所说,我选择在我的d3.js中加入index.html,因此我可以在我的指令中访问它而无需解析承诺。这里可能是并行:FWIW,我使用JQuery对Angular承诺的承诺,所以当我需要JQuery时该怎么办?好吧,我只是在需要它时调用它($.Deferred()),我的观点是以类似的方式调用d3只是对我来说似乎并不令人生气。

虽然我确实使用了d3Service,但它更多地用于帮助函数而不是其他任何函数。例如,当我想获得一个可以工作的SVG时,为什么不调用一个给我一个响应SVG的函数:

指令(链接)

var svg = d3Service.getResponsiveCanvas(scope.id, margin, height, width);

<强>服务

app.service('d3Service', function() {

  return {
      getResponsiveCanvas: function(id, margin, height, width) {
        return d3.select('#' + id)
              .append('div')
              .classed('svg-container', true)
              .append('svg')
              .attr('id', 'svg-' + id)
              .attr('preserveAspectRatio', 'xMinYMin meet')
              .attr('viewBox', '0 0 ' + (width + margin.left + margin.right) + ' ' + (height + margin.top + margin.bottom))
              .classed('svg-content-responsive', true)
              .append('g')
              .attr('transform', 'translate(' + margin.left + ', ' + margin.top + ')');
      }
  }
});

我有类似的功能,可以将轴添加到SVG。这确实有代码味道,但是再次,就其本质而言d3我们直接操纵DOM,所以我的经验就是我们把它放在哪里,它会变得丑陋而不是感觉非常Angular像,所以你不妨制作一些让你的生活更轻松的服务。